By: CS2103-AY2018/19s2-W10-4 Team
Since: Mar 2019
Licence: MIT
- 1. Setting up
- 2. Design
- 3. Implementation
- 4. Documentation
- 5. Testing
- 6. Dev Ops
- Appendix A: Product Scope
- Appendix B: User Stories
- Appendix C: Use Cases
- Appendix D: Non Functional Requirements
- Appendix E: Glossary
- Appendix F: Instructions for Manual Testing
- F.1. Launch and Shutdown
- F.2. Card operations
- F.3. Starting and ending a test session
- F.4. Answering a question in a test session
- F.5. Navigating to the next question in a test session
- F.6. Viewing a report display
- F.7. Sorting the cards
- F.8. Folder operations
- F.9. Navigating in and out of folders
- F.10. Saving data
1. Setting up
1.1. Prerequisites
-
JDK
9
or laterJDK 10
on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK9
. -
IntelliJ IDE
IntelliJ by default has Gradle and JavaFx plugins installed.
Do not disable them. If you have disabled them, go toFile
>Settings
>Plugins
to re-enable them.
1.2. Setting up the project in your computer
-
Fork this repo, and clone the fork to your computer
-
Open IntelliJ (if you are not in the welcome screen, click
File
>Close Project
to close the existing project dialog first) -
Set up the correct JDK version for Gradle
-
Click
Configure
>Project Defaults
>Project Structure
-
Click
New…
and find the directory of the JDK
-
-
Click
Import Project
-
Locate the
build.gradle
file and select it. ClickOK
-
Click
Open as Project
-
Click
OK
to accept the default settings -
Open a console and run the command
gradlew processResources
(Mac/Linux:./gradlew processResources
). It should finish with theBUILD SUCCESSFUL
message.
This will generate all resources required by the application and tests. -
Open link:
MainWindow.java
and check for any code errors-
Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully
-
To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select
Add '--add-modules=…' to module compiler options
for each error
-
-
Repeat this for the test folder as well (e.g. check link:
HelpWindowTest.java
for code errors, and if so, resolve it the same way)
1.3. Verifying the setup
-
Run the
seedu.knowitall.MainApp
and try a few commands -
Run the tests to ensure they all pass.
1.4. Configurations to do before writing code
1.4.1. Configuring the coding style
This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,
-
Go to
File
>Settings…
(Windows/Linux), orIntelliJ IDEA
>Preferences…
(macOS) -
Select
Editor
>Code Style
>Java
-
Click on the
Imports
tab to set the order-
For
Class count to use import with '*'
andNames count to use static import with '*'
: Set to999
to prevent IntelliJ from contracting the import statements -
For
Import Layout
: The order isimport static all other imports
,import java.*
,import javax.*
,import org.*
,import com.*
,import all other imports
. Add a<blank line>
between eachimport
-
Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.
1.4.2. Updating documentation to match your fork
After forking the repo, the documentation will still have the SE-EDU branding and refer to the
cs2103-ay1819s2-w10-4/main
repo.
If you plan to develop this fork as a separate product (i.e. instead of contributing to cs2103-ay1819s2-w10-4/main
)
, you should do the following:
-
Configure the site-wide documentation settings in
build.gradle
, such as thesite-name
, to suit your own project. -
Replace the URL in the attribute
repoURL
in link:DeveloperGuide.adoc
and link:UserGuide.adoc
with the URL of your fork.
1.4.3. Setting up CI
Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.
After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).
Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork. |
Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based) |
1.4.4. Getting started with coding
When you are ready to start coding, get some sense of the overall design by reading Section 2.1, “Architecture”.
2. Design
2.1. Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture .
|
Main
has only one class called link: MainApp
. It is responsible for,
-
At app launch: Initializes the components in the correct sequence, and connects them up with each other.
-
At shut down: Shuts down the components and invokes cleanup method where necessary.
Commons
represents a collection of classes used by multiple other components.
The following class plays an important role at the architecture level:
-
LogsCenter
: Used by many classes to write log messages to the App’s log file.
The rest of the App consists of four components.
Each of the four components
-
Defines its API in an
interface
with the same name as the Component. -
Exposes its functionality using a
{Component Name}Manager
class.
For example, the Logic
component (see the class diagram given below) defines it’s API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
delete 1
commandThe sections below give more details of each component.
2.2. UI component
API : Ui.java
UI
consists of a MainWindow
made up of many parts as seen above e.g.CommandBox
, ResultDisplay
, CardMainScreen
,
StatusBarFooter
, BrowserPanel
etc. All these parts, including the MainWindow
, inherit from the abstract UiPart
class.
The UI
component uses JavaFx UI framework.
The layout of these UI parts are defined in matching .fxml
files located in the src/main/resources/view
folder.
For example, the layout of the link: MainWindow
is
specified in link: MainWindow.fxml
The UI
component,
-
Uses
Logic
component to execute user commands. -
Listens for changes to
Model
data to update UI with the modified data.
2.3. Logic component
API : Logic.java
-
Logic
uses theCardFolderParser
class to parse the user command. -
This results in a
Command
object which is executed by theLogicManager
. -
The command execution can affect the
Model
(e.g. adding a card). -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
. -
In addition, the
CommandResult
object can also instruct theUi
to perform certain actions, such as displaying help to the user.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API call.
delete 1
Command2.4. Model component
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores a list of
VersionedCardFolders
representing the folders that the user has. TheseVersionedCardFolders
in turn each store a list ofCards
. EachCard
stores information about a single question. -
exposes unmodifiable instances of
FilteredList<Card>
andFilteredList<VersionedCardFolder>
that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -
exposes two instances of
SimpleObjectProperty<Card>
, which represent the current cards being selected and being tested, if any. -
does not depend on any of the other three components.
As a more OOP model, we can change the Card implementation to be that of a parent class, from which 2 subclasses, SingleAnswerCard and McqCard can inherit from. This would eliminate the need for the Card class to maintain a Set of MCQ Options even if it is a Single-answer card. An example of how such a model may look like is given below. |
2.5. Storage component
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. It maintains a single instance ofUserPrefStorage
. -
can save
CardFolder
data in json format and read it back. It holds a list ofCardFolderStorage
objects, each handling the storage functions of a singleCardFolder
.
2.6. Common classes
Classes used by multiple components are in the seedu.knowitall.commons
package.
3. Implementation
This section describes some noteworthy details on how certain features are implemented.
3.1. Cards
3.1.1. Current Implementation
The Card
is one of the core aspects of the application. Cards are the result of morphing the Person
class from the original AddressBook model. This implementation incorporates the Model
and Logic
components.
Model
To allow users to manage Cards, the following methods are available via the Model
component:
-
ModelManager#addCard(Card card)
- Adds a new card to the currently activeVersionedCardFolder
folder -
ModelManager#setCard(Card target, Card editedCard)
- Edits the information of a target card in the currently active folder -
ModelManager#deleteCard(Card target)
- Deletes the target card from the currently active folder -
ModelManager#hasCard(Card card)
- Checks if a card is already present in the currently active folder
Logic
As with all other commands, the LogicManager#execute(String commandText)
method of the Logic
component parses the user’s input, say a command to add a new card, and executes the corresponding Command
.
Example Usage
The following steps detail the underlying logic executed when a user does a card-related operation, say an add card operation.
-
User is in the
Organs
folder and wants to add a new card, with question 'What is the largest organ?' and answer 'Skin'. This is done by typingadd q/What is the largest organ? a/Skin
. -
The command parser reads the string input (as entered by the user) and returns the corresponding
Command
, anAddCommand
object in this instance. -
Upon execution, the
AddCommand
checks if the card to be added is already present in the current folder. If so, an exception is thrown. -
The
AddCommand
then calls theModelManager#addCard(Card card)
method. -
The new card will then be added to the active
VersionedCardFolder
. -
If the user is not inside a folder, or if the card to add already exists inside the current folder, the
addCommand
will throw aCommandException
.
The following sequence diagram demonstrates how AddCommand
works.
3.1.2. Design Considerations
Aspect: How to represent options for MCQ cards
-
Alternative 1 (current choice): Maintain a set of
Option
objects to represent incorrect options, separate from theAnswer
field of eachCard
.-
Pros: Simple to implement, easy to convert the card type between MCQ and Single-answer, requires the least amount of implementation changes to
Card
. -
Cons: Single-answer cards still have to maintain an empty
Option
set.
-
-
Alternative 2: Maintain 2 separate subclasses of
Card
, one for Single-answer and another for MCQ.-
Pros: More object-oriented implementation.
-
Cons: Harder to implement.
-
3.2. Score feature
3.2.1. Current Implementation
To implement tracking of the number of correct and incorrect attempts, a new attribute Score
was added to Card
.
Score keeps track of both numbers.
This attribute is encapsulated in the Score
class.
3.2.2. Design Considerations
Aspect: How to represent score
-
Alternative 1 (current choice): Track total number of correct attempts and total number of attempts
-
Pros: Simple to implement. Most semantically correct.
-
Cons: Score will be rolling average. If the question is answered wrongly even once, the score can never be 100%.
-
-
Alternative 2: Track only last X attempts.
-
Pros: An improvement in performance will be more obvious.
-
Cons: Uses more memory. Have to delete the X+1th score every time a new score is added.
-
Aspect: How to read/write score from file
-
Alternative 1 (current choice): Read/write as
String
.-
Pros: `String`s are easier to read/write to file.
-
Cons: There must be strict checks when instantiating score from strings as they are prone to many kinds of formatting errors.
-
-
Alternative 2: Read/write as a double.
-
Pros: A
double
can represent both numbers with just one, which then can be converted toString
. -
Cons: When instantiating score from double, it might be simplified. For example, 2 correct attempts and 4 total attempts becomes 0.5. When instantiating from double, it is interpreted as 1 correct attempt and 2 total attempts.
-
-
Alternative 3: Read and write both numbers as integers.
-
Pros: Most correct implementation.
-
Cons: Reading/writing to file now needs to take into account this fact. The
toString()
method cannot be used to write to file.
-
3.3. Report feature
3.3.1. Current Implementation
The report feature is meant to provide the user with the ability to look back and compare their test score from previous test sessions. It makes use of Java FXML features to display scores in a user-friendly manner. The following methods are used to enter or exit the report display:
-
Model#enterReportDisplay()
— Enters the report display for this folder. -
Model#exitReportDisplay()
— Exits the report display back into the folder.
Here is a screenshot of how it looks:
Given below is an example usage scenario and how the report display is rendered.
Step 1. The user is in a folder and wishes to see a report by executing report
.
Step 2. A ReportCommand
is created and executed by LogicManager
. Model#enterReportDisplay()
is called, which prepares
the model for report by sorting the cards by lowest scores first. The state is also checked to make sure the user is in
a folder.
Step 3. A CommandResult
with type ENTERED_REPORT
is returned by the ReportCommand
.
Below is a sequence diagram, summarizing the above operations.
Step 4. The CommandResult
is passed to UI, which now has to render the new screen. It does so by getting the current
CardFolder
from Model, then instantiating a ReportDisplay
with it. ReportDisplay
prepares several elements for
display by Java FXML, split by their content:
-
ReportDisplay#displayTitle()
— Displays title and how many test scores were recorded. -
ReportDisplay#displayGraph()
— Takes maximum of last 10 test scores fromCardFolder
, and adds them as points on the Java FXMLAreaChart
. -
ReportDisplay#displayQuestions()
— Displays maximum of 3 questions fromCardFolder
which have lowest the individual card scores. -
ReportDisplay#displayTagline()
— Calculates change in test score from last and second last test session (if available) and formats it in a user-friendly manner.
Step 5. The ReportDisplay
is rendered by fullScreenPlaceholder
.
Below is a sequence diagram summarizing steps 4 and 5. The interactions with Java FXML elements are omitted for clarity.
3.3.2. Design Considerations
Aspect: How to display test scores
-
Alternative 1 (current choice): Display previous test scores in a graph and lowest scoring cards
-
Pros: Has benefits of seeing the graph as well as knowing which cards the user needs help in.
-
Cons: More performance and memory intensive. Screen may get messy if there are too many items.
-
-
Alternative 2: Display previous test scores in a graph
-
Pros: More visual, easier to see change in test score.
-
Cons: More performance and memory intensive as a graph needs to be rendered. Remedy: Display only last ten.
-
-
Alternative 3: List the previous test scores
-
Pros: User can see more accurate numbers. They can also see the individual card scores, so that they can tell where they need help.
-
Cons: Hard to see change from test session to test session.
-
Aspect: Where to display test scores
-
Alternative 1 (current choice): Display in full screen, entered from card folder
-
Pros: Works because the test scores are scored by card folders.
-
Cons: Need to implement a new state for commands, because it should not be possible to do some e.g. add card while in fullscreen report.
-
-
Alternative 2: Display in right panel, with cards on the left
-
Pros: User can see all their cards at the same time.
-
Cons: Less space to render report details such as graph.
-
3.4. State
3.4.1. Current Implementation
Model
Previously, Model did not have a concept of state as there was only one screen where the user could be. But as Know-It-All grew, there are more screens that a user can be in and more commands that can only be executed in certain screens. Thus there is a need to manage the state in Model.
3.4.2. Design Considerations
Aspect: How to manage state
-
Alternative 1 (current choice): Use enum of States
-
Pros: Fixes the states that Model can be in. By design, only one state can be true at any point in time, if state is set to type State which is a enum.
-
Cons: Need to add new states to enum.
-
-
Alternative 2: Use Strings
-
Pros: No need to define new states. Trivial to change into new state: Simply set state to "folder", or "homeDir". Easy to check as well, e.g. to Check state == "folder".
-
Cons: Becomes very unsafe as even a typo would mean entering a state that other parts of the application would not understand. e.g. Setting state to "Folder" instead of "folder".
-
-
Alternative 2: Use boolean flags
-
Pros: Easy to manipulate. Clear when there are only two states.
-
Cons: Becomes very messy when there are more states, since there is a need to ensure that only one boolean flag is true at any point in time. E.g., only one of inFolder, inHomeDir, inTest… can be true.
-
3.5. Import/Export feature
Current Implementation
The Import and Export feature is facilitated by the Logic and model components, together with the addition of 3 new classes inside the csvmanager package defined under the storage package.
Logic
Similar to how the current commands are implemented, where each specific command inherits from the abstract class command, the Import and Export command likewise inherit from the command class.
Model
The model component supports the addition of a new object attribute CsvManager
, that carries out the read and
write logic of cards to external files.
Additionally, model implements two new methods, Model#importCardFolders
and Model#exportCardFolders
.
These two methods further call the CsvManager API to read and write card folders.
Model also implements Model#setTestCsvPath
and the Model#getDefaultPath
which are only used during testing.
csvmanager package:
The csvmanager package contains all classes dealing with the reading and writing of card folders into and out to the model.
The classes include:
-
CsvManager
- Main class responsible for the writing and reading of .csv files during the import and export of files -
CsvFile
- Wrapper class for file name input by user -
CsvCommands
interface - API for import/export method in CsvManager -
CsvUtils
- Utility class containing the default path and test paths used by the application.
The main logic for the import / export command is carried out inside the CsvManager class specified by it’s
API CsvManager#writeFoldersToCsv
and CsvManager#readFoldersToCsv
.
Example Usage
-
User wants to export folders Human Anatomy and Nervous System. Suppose that these two folders index as shown on the main application screen is 1 and 3 respectively. The user types
export 1 3
-
The Export command parser extracts the indices, stores the indices into a
List<Integer>
andExportCommand
object, which keeps track of the indices. -
The Export command calls executes, resulting in a call to the
Model#exportCardFolders
method. -
Model#exportCardFolders
method checks that all card folders specified by user exists within the model. With reference to the diagram below, we can see that the indices specified by the user (1 and 3) corresponds to the (i - 1) th indexes of the list of card folders in the model, where 1 < i ⇐ no.of card folders in model.
The method throwsCardFolderNotFoundException
if card folder index is not found in list. -
CsvManager
is called to carry out the main logic of writing the card folders to file. File exported will be created in the project root directory.
Names of the files created will correspond to the names of their corresponding card folders.
i.eHuman Anatomy.csv
andNervous System.csv
-
User wants to import
Human Anatomy.csv
file.Human Anatomy.csv
file contains flashcards represented in csv file format. User typesimport Blood.csv
command -
Import command parser extracts file name, wraps file into a
CsvFile
object and parses theCsvFile
object into an Import Command object. -
Logic unit executes the import command. The execute method makes a call to
Model#importCardFolder
method. The model checks that the card folder to import does not already exist within the model. ThrowsDuplicateCardFolderException
if card folder already exists. -
CsvManager is called to carry out the main logic of reading card folders to file. File imported will be serialized into a
CardFolder
object and added to thefolders
variable in model.
Both Imported and Exported files have to be placed in the project root directory. |
The below diagram shows the sequence diagram for the Import/Export commands
3.5.1. CSV file structure
Example of a common cardfolder csv file, opened using microsoft excel.
Blood.csv
-
The first line of any file contains the headers for each card. These headers have to be present in the csv file. If not the import would not work.
-
Each row of the csv file starting from the second, represents a single flashcard.
-
Option headers i.e (Option,Option,Option) can be left blank or take on 1 value.
-
Hints header can take either 0 or 1 value.
3.5.2. Design Considerations
Aspect: Which component responsible for import/export logic
-
Alternative 1 (current choice): Implement read and write card folders in StorageManager class
-
Pros: The most intuitive solution, since Storage involves read and write logic
-
Cons:
-
Either Logic and Model will be more coupled with storage. Storage also has more than one responsibility now.
-
-
-
Alternative 2:Implement read/write and other logic in another class. (Current)
-
Pros: Separate responsibilities of both Storage and Model. Model class can focus on the representation of the in-memory card folders and Storage class can focus on managing the internal card folder data (.json files)
-
Cons: More code to write. Storage class could possibly call the relevant API’s to convert .json file into .csv file
-
Aspect: Csv file design structure
-
Alternative 1: Export multiple card folders into a single file.
-
Pros: Saves user trouble of calling multiple import for files.
Each card folder is separated by a new line. -
Cons: Not a .csv file anymore. First line header would now specify cardfolder name before card headers, leading to unequal rows and columns
-
-
Alternative 2: Export each card folder into a single file (Current)
-
Pros: More flexibility for users to import desired card folders, since 1 cardfolder = 1 csv file. Files are also now correctly formatted as .csv file and Storage class can focus on managing the internal card folder data (.json files)
-
Cons: Slightly more work needed to import multiple card folders.
-
3.6. Folders
3.6.1. Current Implementation
A folder is another layer of abstraction over a CardFolder
. Where we dealt with a single CardFolder
in previous iterations, we now have multiple CardFolders
that each have their own set of Cards
. Users are able to manage each CardFolder
independently.
Folders in the application are achieved via enhancements from the AddressBook implementation. The changes span across all four components (UI, Logic, Model and Storage).
Model
Previously, an instance of ModelManager
contained only a single VersionedCardFolder
, holding the current and previous state of the CardFolder
. To support multiple folders, ModelManager
now holds an ObservableList
of CardFolders
. The change is illustrated in the figure below, with the original implementation on the left and new implementation on the right.
To allow users to operate on multiple CardFolders
, the following notable methods were also introduced:
-
ModelManager#addFolder(CardFolder cardfolder)
- Adds a specified cardfolder to theModelManager
’s list -
ModelManager#deleteFolder(int index)
- Deletes theCardFolder
at the specified index in theModelManager’s
list -
ModelManager#getActiveCardFolderIndex()
- Gets the index of the current activeCardFolder
-
ModelManager#enterFolder(int index)
- Specifies the activeCardFolder
for operations to be performed on via the index inModelManager
’s list and sets the booleaninFolder
totrue
to denote that user is inside a folder. -
ModelManager#exitFolderToHome()
- Sets the booleaninFolder
tofalse
to indicate that the user is at the home directory. -
ModelManager#renameFolder(int index, String newName)
- Renames the folder at the specified index in theModelManager’s
list to the new name. -
ModelManager#getState()
- Returns the current state of theModelManager
. The possible states are {IN_FOLDER
,IN_HOMEDIR
,IN_TEST
,IN_REPORT
}.
Storage
Similarly, the StorageManager
needs to represent each CardFolder
separately. In the same manner as in the Model component, we introduce a list of JsonCardFolderStorages
. The change is illustrated in the figure below, with the original implementation on the left and new implementation on the right.
Notable new methods:
-
StorageManager#readCardFolders()
- Reads in allCardFolders
from allCardFolderStorage
objects in the list. -
StorageManager#saveCardFolders(List<ReadOnlyCardFolder> cardFolders)
- Saves allCardFolders
provided in the argument to the user’s data directory.
Logic
The existing implementation of the Logic component propagates changes in a Model’s CardFolder
to the Storage component. With listeners, it is informed when a CardFolder
is modified (e.g. a new card is added) so that it can invoke the appropriate Storage methods.
The same principle was applied to propagate changes regarding CardFolders
themselves (and not their stored cards) to Storage: e.g. adding a new folder. Model is now an Observable
, and changes to a Model’s CardFolders
will inform the LogicManager
, which in turn invokes StorageManager#saveCardFolders(List<ReadOnlyCardFolder> cardFolders)
.
To illustrate how the Model, Storage and Logic components interact, below is a walkthrough of a typical usage scenario of the addfolder
command. Figure 9, “Component interactions for an addfolder
command” is a sequence diagram that summarises the example:
-
Step 1. The
addfolder
command is executed. For example,addfolder f
. -
Step 2. As with every command, the command parser reads the input and generates the relevant
Command
object, in this case anAddFolderCommand
. The object is returned to theLogicManager
.
If the input is invalid (e.g. user did not provide a folder name or provided one that violated the constraints of folder names), Step 2 would not proceed and an error message is displayed. The Model and Storage components would not be modified. |
-
Step 3. The
LogicManager
executes theAddFolderCommand
, Before transferring control to the Model component with theModelManager#addFolder()
method, a few checks are performed to ensure that the user is inside a folder and the model does not already have a folder with the same name (not shown in sequence diagram).
If any of the aforementioned checks do not succeed, Step 3 would end in a Command Exception being thrown and would not proceed. Model and Storage components would not be modified. |
-
Step 4. The
ModelManager
creates aVersionedCardFolder
to represent the newly created folder, storing a reference to its currently empty list of cards. Before returning control to the Logic component,ModelManager#indicateModified()
is invoked to notify listeners in theLogicManager
that the list ofCardFolders
have changed. -
Step 5. The Logic component takes over control and checks if the
ModelManager
is modified. In the case ofaddfolder
the object is indeed modified (as a result of Step 4) and thus the component proceeds to save the Model’sCardFolders
to Storage. -
Step 6. Before handing over control to Storage, the
LogicManager
obtains the information to save and the path to save to withModelManager#getCardFolders()
andModelManager#getCardFoldersFilesPath()
respectively. It then passes these objects as parameters when it callsStorageManager#saveCardFolders()
. -
Step 7. The Storage component receives control, with the
StorageManager
clearing the directory of data files at the specified path and creatingJsonCardFolderStorage
objects with path names equivalent to the names of the folders it has received. It then proceeds to invokeJsonCardFolderStorage#saveCardFolder()
on all theJsonCardFolderStorage
to save all the folders before returning to theLogicManager
.
If the path provided by the Model Component is invalid, the Storage component throws an exception and an error message is displayed. The changes made to Model are not saved and the command does not execute successfully. |
-
Step 8. The
LogicManager
terminates and returns the result of the command to the calling method.
addfolder
commandUI
As folders are a layer of abstraction over the cards, there is a need for the GUI to represent this abstraction for greater clarity and ease of use for the user. This is done by introducing the FolderListPanel
class, which displays a list of all folders that the user has.
The fullScreenPlaceholder:StackPane
object houses the content in the main window of our application. Depending on whether the user is in the home directory or within a folder, different UI objects are placed within fullScreenPlaceholder
.
-
When the user is in the home directory,
fullScreenPlaceholder
holds aFolderListPanel
to display all the folders in a list inside the main window. -
When the user is within a folder,
fullScreenPlaceholder
holds aCardMainScreen
object, which is composed of aCardListPanel
andBrowserPanel
. These represent the list of cards on the scrolling sidebar, as well as the card viewer on the right. The content within theCardMainScreen
depends on the particular folder the user has navigated into, as different folders hold different cards.
To better understand how the UI is updated, below is a walkthrough of what happens when the user enters a folder. Refer to the sequence diagram in Figure 10, “UI behaviour when user enters folder” for a visual representation:
-
Step 1. The Logic component informs the UI component that the user has entered a folder. The UI component responds by invoking
MainWindow#handleEnterFolder()
. -
Step 2. UI retrieves the list of cards belonging to the entered folder from the
LogicManager
. -
Step 3. A new
CardListPanel
is created with the information obtained in Step 2. -
Step 4. The new
CardListPanel
from Step 3, together with the existingBrowserPanel
, are used to create a newCardMainScreen
object. -
Step 5. The content held by
fullScreenPlaceholder
is replaced with the newly generatedCardMainScreen
.
3.6.2. Design Considerations
Aspect: How multiple folders are represented in Model
-
Alternative 1 (current choice): List of structures representing individual folders
-
Pros: Scalable and better follows OOP principles.
-
Cons: Hard to implement, alters fundamental architecture of components.
-
-
Alternative 2: A single structure containing
Cards
with information on their folder membership (folder operations performed by iterating over all cards)-
Pros: Easy to implement.
-
Cons: Not scalable, will be computationally expensive to perform folder operations when there are many cards and/or folders.
-
Evaluation: On the account of how computationally expensive it would be to parse through thousands of cards every card or folder operation, the first alternative was chosen and time was set aside to overcome the technical challenge of implementing the choice.
Aspect: Folder identification
-
Alternative 1: Use a unique folder name
-
Pros: Easier to implement.
-
Cons: The undo/redo feature would not be compatible with this approach, as checking equality between different versions of a folder across time necessarily requires the comparison of cards.
-
-
Alternative 2: Identify a folder by its cards
-
Pros: There can be no folders with identical cards, preventing redundancy.
-
Cons: Two folders could have identical names as long as the cards are different, which is potentially confusing.
-
-
Alternative 3 (current choice): Mixed approach, use Alternative 1 for comparing different folders and Alternative 2 for comparing the same folder across time
-
Pros: Reaps the benefits of both approaches without the disadvantages.
-
Cons: Difficult to implement and for future developers to grasp the difference between the two types of comparisons.
-
Evaluation: While it was difficult the implement, Alternative 3 was chosen due to the immense limitations of the first two approaches. The third alternative had little to no downside apart from requiring time to understand and implement.
Aspect: Storage file name and folder name
-
Alternative 1: Let folder name be the file name of the storage file
-
Pros: Less ambiguity as to how file name is related to folder name, able to find storage file path with folder name.
-
Cons: Harder to retrieve folder name from the file as it requires parsing the path, more prone to data corruption as file name could be modified when application is running (although this could be overcome with some OS-level syscalls to lock the file).
-
-
Alternative 2 (current choice): Let file name be independent of folder name, which is stored inside the storage file itself
-
Pros: Easier to implement and avoids dependency on existing storage files after application starts.
-
Cons: When saving folders from Model, it is difficult to match folders with existing storage files. Hence, rather than saving the modified folder, it is more feasible to clear the directory and save all folders. This is computationally expensive and may not be scalable beyond a certain size.
-
Evaluation: While in the long-run, the first alternative appears to better decision, time limitations make Alternative 2 good enough for most practical use cases. The saved time was used to implement other features.
Aspect: What folders to generate in the event corrupted storage files are encountered
-
Alternative 1: Display a sample folder
-
Pros: Easy to implement, guaranteed that application will not be empty with no folders displayed.
-
Cons: Non-corrupted folders will not be displayed and will potentially be overwritten.
-
-
Alternative 2: Display non-corrupted folders
-
Pros: Non-corrupted data is preserved.
-
Cons: If all data is corrupted, an empty application is presented to the user.
-
-
Alternative 3 (current choice): Mixed approach, display all non-corrupted folders unless all data is corrupted, in which case display sample folder
-
Pros: Has the advantages but not the disadvantages of Alternatives 1 and 2.
-
Cons: More difficult to implement, developers will need to pay special attention to understand this behaviour.
-
Evaluation: Alternative 3 was deemed the most logical choice because it achieves the important objectives of retaining non-corrupted user data, while always ensuring there is data for the user to work with even if he/she is starting the application for the first time.
3.7. Navigating folders
3.7.1. Current Implementation
Navigation State
The state of the application with regard to navigation (i.e. whether a user is inside of a folder or at the home directory) affects the types of commands available to the user.
-
The commands that affect cards (e.g. adding a card, editing a card) are executed within folders and are known as Card Operations.
-
Commands that affect folders (e.g. adding a folder, deleting a folder) are only executable at the home directory and are known as Folder Operations.
Please refer to the User Guide for the full list of commands under both categories.
To keep track of navigation state, an enum State
is maintained by the ModelManager
. Other components may retrieve the current state with ModelManager#getState()
. This is also how the Command
objects determines whether the command is executable in the present navigation state.
Change Directory Command
Folder navigation is achieved by the user through the use of the cd
command. As navigating folders do not actually modify folders and their cards, folder navigation does not involve the Storage Component.
The change directory command has the following formats:
-
cd ..
- Returns the user to the home directory. This command can only be executed when the user is inside a folder. -
cd FOLDER_INDEX
- Enters the folder specified byFOLDER_INDEX
. This command can only be executed from the home directory, when the user is not in any folder.
When a cd
command is executed, the Logic component parses the command and creates a ChangeDirectoryCommand
object. If the command is of the first format, ChangeDirectoryCommand()
is invoked without any arguments and the boolean isExitingFolder
is set to true
. If the command is of the second format, the overloaded constructor ChangeDirectoryCommand(FOLDER_INDEX)
is instead called and isExitingFolder
is set to false
.
ChangeDirectoryCommand#execute()
is then invoked. The value of isExitingFolder
will determine the corresponding methods in ModelManager
that are called (exitFoldersToHome()
or enterFolder()
). The sequence diagram in Figure 11, “Component interactions for cd
command” illustrates this conditional choice and the interactions involved with each option.
cd
command3.7.2. Design Considerations
Aspect: Command format to enter and exit folders
-
Alternative 1 (current choice): Use variations of the same command (e.g.
cd ..
andcd INDEX
)-
Pros: More intuitive and akin to other Command Line applications.
-
Cons: Harder to implement as the logic for parsing the command is different from that of existing commands.
-
-
Alternative 2: Use distinct commands (e.g.
home
andenter INDEX
)-
Pros: Commands would function similar to other commands in the application.
-
Cons: Harder for the user to get acquainted with as there are two separate commands with logically similar functionality; introduces redundancy.
-
Evaluation: This matter is highly subjective and conflicting feedback was received during testing. However, we believe that our target audience, a user familiar with the command line, would be more used to navigation with a single command and as such would prefer Alternative 1.
3.8. Test Session feature
3.8.1. Overall Current Implementation
This big feature mainly involves UI
, Logic
and Model
components.
There are 3 main variables in ModelManager
introduced to keep track of the current state of the user in a test
session.
-
currentTestedCardFolder
-
The current card folder that the user is running a test session on (stored as an
ObservableList
of cards) . -
Set to null if user is not inside a test session
-
-
currentTestedCard
-
The current card the user is seeing in the test session, obtained from
currentTestedCardFolder
usingcurrentTestedCardIndex
. -
Set to null if user is not inside a test session
-
Related methods:
-
ModelManager#setCurrentTestedCard(Card card)
- setcurrentTestedCard
to the card specified. -
ModelManager#getCurrentTestedCard()
- returns thecurrentTestedCard
.
-
-
-
cardAlreadyAnswered
-
A boolean variable to indicate if the user has already executed a valid answer command for the current card.
-
Related methods:
-
ModelManager#setCardAsAnswered()
- setcardAlreadyAnswered
to true. -
ModelManager#setCardAsNotAnswered()
- setcardAlreadyAnswered
to false. -
ModelManager#isCardAlreadyAnswered()
- returns true if the current card has already been answered and false otherwise.
-
-
3.8.2. Overall Design Considerations
Aspect: Usage of an extra card variable to keep track of the current card in test session
-
Alternative 1 (current choice): Introduce another variable,
currentTestedCard
, to store the card to display in the test session.-
Pros: More reader friendly. Save time from accessing the list to get card at that index.
-
Cons: Extra space used.
-
-
Alternative 2: No introduction of
currentTestedCard
as using thecurrentTestedCardIndex
suffices. Every time a card is needed, can simply reference it usingcurrentTestedCardFolder#getIndex(currentTestedCardIndex)
.-
Pros: No need to store an extra variable so this method uses less space.
-
Cons: Not so reader friendly. Need to keep accessing the list using the index which can potentially lead to possible violation of the Law of Demeter where an object should only interact with objects that are closely related to it.
-
Evaluation:
We went with Alternative 1 since not a large amount of memory is taken up with just 1 extra card stored. As there will
be several references to the currentTestedCard
, it will be better to store them somewhere. Abiding by the Law
of Demeter, currentTestedCard
object will not be interacting with currentTestedCardFolder
, limiting its
knowledge of that object which is encouraged according to the Principle of Least Knowledge.
3.8.3. Test / End Command
Current Implementation
Model
The main logic for test
and end
command is carried out in ModelManager
with the following methods:
-
ModelManager#startTestSession()
- begins a test session on the current card folder user is in and implicitly sorts the cards in it. -
ModelManager#endTestSession()
- ends the current test session.
UI
To update the change in the UI to reflect that the user is a test session (app goes to full screen with question of the current card presented), the following methods are introduced.
-
MainWindow#handleStartTestSession(Card card)
- creates a newTestSession
page with the card specified and bring the page forward in front of the currentCardMainScreen
page. -
MainWindow#handleEndTestSession()
- deletes the currentTestSession
page and theCardMainScreen
page at the back is now presented to the user.
Example Usage of test command
To illustrate how the UI
, Model
and Logic
components interact, below is a walkthrough of a typical usage scenario of
the test command.
Figure 12, “Component interactions for a test command” is a sequence diagram that summarises Model
and Logic
interactions, namely steps 1 to 7.
Step 1. User is inside a folder and wants to begin a test session on the current folder by executing the command
test
.
Step 2. As with every command, the command parser reads the input and generates the relevant Command
object, in
this case a TestCommand
. The object is returned to the LogicManager
.
Step 3. The LogicManager
executes the TestCommand
, storing the result and then transferring control to the
Model component, with Model#getState()
to check that user is inside a folder and is not already in a test session.
(This is omitted from Figure 12, “Component interactions for a test command” for simplicity.)
If the user is not inside a folder, this test command would be rendered invalid. Step 4 would not proceed and an error message is displayed. |
Step 4. After checking user is in a folder, ModelManager#startTestSession()
method is invoked, which
does the following:
-
currentTestedCardFolder
is set to the current folder the user is in, by invokinggetCardList()
from the activeVersionedCardFolder
infolders
.If this folder is empty such that there is no card to present to the user, an EmptyCardFolderException
is thrown, to be caught inTestCommand
, which then throws aCommandException
to display an error message. -
The cards in this folder are sorted in ascending scores by invoking
sortFilteredCard(comparator)
, so that lowest score cards will be presented first to the user in a test session. -
ModelManager#setCurrentTestedCard(currentTestedCardIndex)
is then invoked to setcurrentTestedCard
to the first card in the folder ascurrentTestedCardIndex
is set to 0. -
state
inModel
is set toIN_TEST
to specify that user is in a test session from now onwards. -
No change to
cardAlreadyAnswered
as it is by default false.
Step 5. For TestCommand
to obtain the first card to present in the test session,
ModelManager#getCurrentTestedCard()
is invoked and the Card
, c
, is returned.
Step 6. With control now transferred back to the logic unit, TestCommand
creates a CommandResult
object, r
with the type START_TEST_SESSION
, and
set testSessionCard
in CommandResult
to c
obtained in Step 5.
Step 7. r
is returned to LogicManager
which then terminates and returns r
to the caller method.
Step 8. The caller method here is in MainWindow
. Control is now transferred to the UI component.
Step 9. MainWindow
sees that CommandResult
object r
has the type START_TEST_SESSION
. It invokes
MainWindow#handleStartTestSession(currentTestedCard)
to display the currentTestedCard
question and hints to the
user.
Example Usage of end command
Step 1. User is currently in a test session and executes the command end
.
Step 2. An EndCommand
object is created and LogicManager
executes the EndCommand
, storing the result and then
transferring control to the Model component, with Model#getState()
to check that user is indeed in a test session.
If the user is not in a test session, Step 3 would not proceed. |
Step 3. ModelManager#endTestSession()
method is invoked, which does the following:
-
currentTestedCardFolder
is set to null. -
ModelManager#setCurrentTestedCard(null)
is invoked to setcurrentTestedCard
to null. -
state
inModel
is set toIN_FOLDER
to specify that user is back in the folder. -
ModelManager#setCardAsNotAnswered()
is invoked.
Step 4. As control is now transferred back to the logic unit, EndCommand
creates a CommandResult
object, r
with
the type END_TEST_SESSION
.
Step 5. r
is returned to LogicManager
which then terminates and returns r
to the caller in MainWindow
. Control is now transferred to the UI component.
Step 6. MainWindow
sees that CommandResult
object r
has the type END_TEST_SESSION
. It invokes
MainWindow#handleEndTestSession()
to delete the current testSession
page, presenting cardMainScreen
page at the
back (the screen the user was seeing before entering the test session) to the user.
Design Considerations
Aspect: Way to execute a test/end command
-
Alternative 1 (current choice):
test
is executed when inside a folder. The user does not have to specify the folder index andtest
would just immediately display the first card in this current folder.-
Pros: The most logical way of carrying out a test session is when the user is in the folder that he or she wants to be tested on. Lesser dependency on entering and exiting folder methods.
-
Cons: Requires the extra step of entering the folder before it can test the folder. User may actually see the questions before the test session.
-
-
Alternative 2:
test
is executed when outside a folder, in the home directory. Test command would require a folder index, e.gtest 1
to test the first folder. Implementation of getting the card from the folder would rely on enter folder command as well.-
Pros: Fast way to enter test session from home directory
-
Cons: Not logical for
test
to be called from inside the home directory which should only allow folder operations.test
will then have to implicitly enter the folder to gain access to the cards in it in order to display them, creating a dependency between the test and enter folder commands. Similar issue will arise for the end test session command where user will need to implicitly exit the folder.
-
Evaluation: Overall, Alternative 1 is a better choice following the Single Level of Abstraction Principle(SLAP) where a function should not mix different levels of abstractions. We can then better achieve higher cohesion and lower coupling. Also, user being able to see the questions before the test session is not a big issue since the answer will not be shown unless user selects the card.
3.8.4. Answer Command
Current Implementation
Model
To facilitate the marking of attempted answer, we introduce the following method in ModelManager
.
-
ModelManager#markAttemptedAnswer(Answer attemptedAnswer)
- returns true if attemptedAnswer is correct and false if attemptedAnswer is wrong. It compares the attempted answer and the correct answer obtained from the current card.
Comparison is not case-sensitive |
Logic
To facilitate the update of score after marking the card, we introduce the following method in ModelManager
.
-
ModelManager#createScoredCard(Card cardToMark, boolean markCorrect)
- creates a new card with the updated score.
UI
To update the change in the UI
to show the user the result of the marked answer, whether it is correct or wrong, the following methods are introduced.
-
MainWindow#handleCorrectAnswer()
- updates currentTestSession
page to green colour background with correct answer and correct answer description. -
MainWindow#handleWrongAnswer()
- updates currentTestSession
page to red colour background with correct answer and wrong answer description.
Example Usage of answer command
To illustrate how the components interact, below is a walkthrough of a typical usage scenario of the answer command.
Figure 13, “Component interactions for a answer command” is a sequence diagram that summarises Model
and Logic
interactions, namely steps 1
to 8.
Step 1. User is in a test session and wants to answer the question on the card currently presented by executing
ans four
.
Step 2. An AnswerCommand
object is created and LogicManager
executes the AnswerCommand
, storing the result and
then transferring control to the Model component, with Model#getState()
and
ModelManager#isCardAlreadyAnswered()
to check that user is indeed in a test session and has not attempted an answer
already. (This is omitted from Figure 13, “Component interactions for a answer command” for simplicity.)
If the user is not in a test session or already attempted an answer for the current card, this ans command would be
rendered invalid. Step 3 would not proceed and an error message is displayed.
|
Step 3. After both checks have passed, ModelManager#setCardAsAnswered()
is invoked.
Step 4. ModelManager
now marks the attempted answer by invoking ModelManager#markAttemptedAnswer(attemptedAnswer)
.
Step 5. Given the result of the attempt, a new card exactly the same as the current
card but with the updated score is created and replaces the current one by invoking ModelManager#createScoredCard(Card
cardToMark, boolean markCorrect)
followed by Model#setCard(cardToMark, scoredCard)
.
Step 6. To complete the update in the change in score, Model#updateFilteredCard(PREDICATE_SHOW_ALL_CARDS)
and
Model#commitActiveCardFolder()
are invoked.
Step 7. AnswerCommand
now creates a CommandResult
object, r
with either type ANSWER_CORRECT
or ANSWER_WRONG
depending on the outcome of the attempt.
Step 8. r
is returned to LogicManager
which then terminates and returns r
to the caller.
Step 9. The caller method is in MainWindow
. Control is now transferred to the UI component.
Step 10. If MainWindow
sees that CommandResult
object r
has the type ANSWER_CORRECT
, it invokes
MainWindow#handleCorrectAnswer()
to display the correct attempt TestSession
page to the user.
If MainWindow
sees that CommandResult
object r
has the type ANSWER_WRONG
, it invokes
MainWindow#handleWrongAnswer()
to display the wrong attempt TestSession
page to the user.
3.8.5. Next Command
Current Implementation
Model
The following method is introduced in ModelManager
to display to the user the next card in the test session.
ModelManager#testNextCard()
- returns true if it successfully finds a next card to present in the test session and
false otherwise (if there is no more cards left to test in the folder).
UI
To display the next card in the test session, the method below is introduced in MainWindow
.
-
MainWindow#handleNextCardTestSession(Card card)
- deletes the currentTestSession
page and adds a newTestSession
page with this next card specified.
Example Usage of next command
To illustrate how the UI
, Model
and Logic
components interact, below is a walkthrough of a typical usage scenario of
the next command.
Figure 14, “Component interactions for a next
command” is a sequence diagram that summarises Model
and Logic
interactions, namely steps 1
to 6.
Step 1. User is in a test session and wants to move on to the next card by executing a next
.
Step 2. A NextCommand
object is created and LogicManager
executes the NextCommand
, storing the result and
then transferring control to the Model component, with Model#getState()
and
ModelManager#isCardAlreadyAnswered()
to check that user is indeed in a test session and has attempted an
answer already. (This is omitted from Figure 14, “Component interactions for a next
command” for simplicity.)
If the user is not in a test session or has not attempted an answer for the current card, this next command would be
rendered invalid. Step 3 would not proceed and an error message is displayed.
|
Step 3. After both checks have passed, ModelManager#testNextCard()
method is invoked, which does the following:
-
currentTestedCardIndex
incremented by 1. -
currentTestedCardIndex
is then checked if it equals to the size ofcurrentTestedCardFolder
.-
Case 1: This check returns true.
This meanscurrentTestedCardIndex
is invalid and there is no more next card to be presented to the user. This method immediately returns false. -
Case 2: This check returns true.
This meanscurrentTestedCardIndex
is valid and will be used to get the next card fromcurrentTestedCardFolder
. This card is set as thecurrentTestedCard
via theModelManager#setCurrentTestedCard (cardToTest)
.ModelManager#setCardAsNotAnswered
is then invoked to reset the value ofcardAlreadyAnswered
. This method returns true.
-
Step 4. From the result of ModelManager#testNextCard()
method earlier:
-
Case 1: Method returns false.
Anext
command will be equivalent to anend
command.ModelManager#endTestSession()
is invoked. Step 5 does not proceed. Instead, step 3 and onwards of the Example Usage of end command takes over. -
Case 2: Method returns true. For
NextCommand
to obtain the next card to present in the test session,ModelManager#getCurrentTestedCard()
is invoked and theCard
,c
, is returned.
Step 5. With control now transferred back to the logic unit, NextCommand
creates a CommandResult
object, r
with
the type SHOW_NEXT_CARD
, and set testSessionCard
in CommandResult
to c
.
Step 6. r
is returned to LogicManager
which then terminates and returns r
to the caller method.
Step 7. The caller method here is in MainWindow
. Control is now transferred to the UI component.
Step 8. MainWindow
sees that CommandResult
object r
has the type SHOW_NEXT_CARD
. It invokes
MainWindow#handleNextCardTestSession(currentTestedCard)
to display this new currentTestedCard
question and hints
to the user.
next
commandDesign Considerations
Aspect: Behavior of next command executed on the last card
-
Alternative 1 (current choice): A
next
command will be equivalent to anend
command-
Pros: More convenient and user-friendly. It is common sense to end the test session for the user.
-
Cons: By right, it is not correct since next command is just to show the next card.
-
-
Alternative 2: A
next
command will throw an exception-
Pros: Most correct way to do it since there is no next card to display.
-
Cons: User may not understand. It is not user-friendly as user has to keep track of which card it is at to prevent the exception thrown.
-
Evaluation: With our target audience in mind, Alternative 1 is the more user friendly and intuitive way to handle this scenario.
3.9. Undo/Redo feature
The following section details a feature implemented in the earlier iteration of the application. As such, the diagrams still refer to AddressBook , which has since replaced with CardFolder . The outdated diagrams here will be updated by v2.0 .
|
3.9.1. Current Implementation
The undo/redo mechanism is facilitated by VersionedCardFolder
.
It extends CardFolder
with an undo/redo history, stored internally as an cardFolderStateList
and currentStatePointer
.
Additionally, it implements the following operations:
-
VersionedCardFolder#commit()
— Saves the current card folder state in its history. -
VersionedCardFolder#undo()
— Restores the previous card folder state from its history. -
VersionedCardFolder#redo()
— Restores a previously undone card folder state from its history.
These operations are exposed in the Model
interface as Model#commitCardFolder()
, Model#undoCardFolder()
and Model#redoCardFolder()
respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedCardFolder
will be initialized with the initial card folder state, and the currentStatePointer
pointing to that single card folder state.
Step 2. The user executes delete 5
command to delete the 5th card in the card folder. The delete
command calls Model#commitCardFolder()
, causing the modified state of the card folder after the delete 5
command executes to be saved in the cardFolderStateList
, and the currentStatePointer
is shifted to the newly inserted card folder state.
Step 3. The user executes add q/Some question …
to add a new card. The add
command also calls Model#commitCardFolder()
, causing another modified card folder state to be saved into the cardFolderStateList
.
If a command fails its execution, it will not call Model#commitCardFolder() , so the card folder state will not be saved into the cardFolderStateList .
|
Step 4. The user now decides that adding the card was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#undoCardFolder()
, which will shift the currentStatePointer
once to the left, pointing it to the previous card folder state, and restores the card folder to that state.
If the currentStatePointer is at index 0, pointing to the initial card folder state, then there are no previous card folder states to restore. The undo command uses Model#canUndoCardFolder() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.
|
The following sequence diagram shows how the undo operation works:
The redo
command does the opposite — it calls Model#redoCardFolder()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the card folder to that state.
If the currentStatePointer is at index cardFolderStateList.size() - 1 , pointing to the latest card folder state, then there are no undone card folder states to restore. The redo command uses Model#canRedoCardFolder() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
|
Step 5. The user then decides to execute the command list
. Commands that do not modify the card folder, such as list
, will usually not call Model#commitCardFolder()
, Model#undoCardFolder()
or Model#redoCardFolder()
. Thus, the cardFolderStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitCardFolder()
. Since the currentStatePointer
is not pointing at the end of the cardFolderStateList
, all card folder states after the currentStatePointer
will be purged. We designed it this way because it no longer makes sense to redo the add q/Some question …
command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
3.9.2. Design Considerations
Aspect: How undo & redo executes
-
Alternative 1 (current choice): Saves the entire card folder.
-
Pros: Easy to implement.
-
Cons: May have performance issues in terms of memory usage.
-
-
Alternative 2: Individual command knows how to undo/redo by itself.
-
Pros: Will use less memory (e.g. for
delete
, just save the card being deleted). -
Cons: We must ensure that the implementation of each individual command are correct.
-
Aspect: Data structure to support the undo/redo commands
-
Alternative 1 (current choice): Use a list to store the history of card folder states.
-
Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
-
Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both
HistoryManager
andVersionedCardFolder
.
-
-
Alternative 2: Use
HistoryManager
for undo/redo-
Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.
-
Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as
HistoryManager
now needs to do two different things.
-
3.10. Logging
We are using java.util.logging
package for logging. The LogsCenter
class is used to manage the logging levels and logging destinations.
-
The logging level can be controlled using the
logLevel
setting in the configuration file (See Section 3.11, “Configuration”) -
The
Logger
for a class can be obtained usingLogsCenter.getLogger(Class)
which will log messages according to the specified logging level -
Currently log messages are output through:
Console
and to a.log
file.
Logging Levels
-
SEVERE
: Critical problem detected which may possibly cause the termination of the application -
WARNING
: Can continue, but with caution -
INFO
: Information showing the noteworthy actions by the App -
FINE
: Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size
3.11. Configuration
Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json
).
4. Documentation
We use asciidoc for writing documentation.
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. |
4.1. Editing Documentation
See UsingGradle.adoc to learn how to render .adoc
files locally to preview the end result of your edits.
Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc
files in real-time.
4.2. Publishing Documentation
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
4.3. Converting Documentation to PDF format
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.
Here are the steps to convert the project documentation files to PDF format.
-
Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the
docs/
directory to HTML format. -
Go to your generated HTML files in the
build/docs
folder, right click on them and selectOpen with
→Google Chrome
. -
Within Chrome, click on the
Print
option in Chrome’s menu. -
Set the destination to
Save as PDF
, then clickSave
to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.
4.4. Site-wide Documentation Settings
The link: build.gradle
file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.
Attributes left unset in the build.gradle file will use their default value, if any.
|
Attribute question | Description | Default value |
---|---|---|
|
The question of the website. If set, the question will be displayed near the top of the page. |
not set |
|
URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar. |
not set |
|
Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. |
not set |
4.5. Per-file Documentation Settings
Each .adoc
file may also specify some file-specific asciidoc attributes which affects how the file is rendered.
Asciidoctor’s built-in attributes may be specified and used as well.
Attributes left unset in .adoc files will use their default value, if any.
|
Attribute question | Description | Default value |
---|---|---|
|
Site section that the document belongs to.
This will cause the associated item in the navigation bar to be highlighted.
One of: * Official SE-EDU projects only |
not set |
|
Set this attribute to remove the site navigation bar. |
not set |
4.6. Site Template
The files in link: docs/stylesheets
are
the CSS stylesheets of the site.
You can modify them to change some properties of the site’s design.
The files in link: docs/templates
controls the rendering of .adoc
files into HTML5.
These template files are written in a mixture of Ruby and Slim.
Modifying the template files in link: |
5. Testing
5.1. Running Tests
There are three ways to run tests.
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. |
Method 1: Using IntelliJ JUnit test runner
-
To run all tests, right-click on the
src/test/java
folder and chooseRun 'All Tests'
-
To run a subset of tests, you can right-click on a test package, test class, or a test and choose
Run 'ABC'
Method 2: Using Gradle
-
Open a console and run the command
gradlew clean allTests
(Mac/Linux:./gradlew clean allTests
)
See UsingGradle.adoc for more info on how to run tests using Gradle. |
Method 3: Using Gradle (headless)
Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.
To run tests in headless mode, open a console and run the command gradlew clean headless allTests
(Mac/Linux: ./gradlew clean headless allTests
)
5.2. Types of tests
We have two types of tests:
-
GUI Tests - These are tests involving the GUI. They include,
-
System Tests that test the entire App by simulating user actions on the GUI. These are in the
systemtests
package. -
Unit tests that test the individual components. These are in
seedu.knowitall.ui
package.
-
-
Non-GUI Tests - These are tests not involving the GUI. They include,
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.knowitall.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.knowitall.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g.seedu.knowitall.logic.LogicManagerTest
-
5.3. Troubleshooting Testing
Problem: HelpWindowTest
fails with a NullPointerException
.
-
Reason: One of its dependencies,
HelpWindow.html
insrc/main/resources/docs
is missing. -
Solution: Execute Gradle task
processResources
.
6. Dev Ops
6.1. Build Automation
See UsingGradle.adoc to learn how to use Gradle for build automation.
6.2. Continuous Integration
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
6.3. Coverage Reporting
We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.
6.4. Documentation Previews
When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.
6.5. Making a Release
Here are the steps to create a new release.
-
Update the version number in link:
MainApp.java
. -
Generate a JAR file using Gradle.
-
Tag the repo with the version number. e.g.
v0.1
-
Create a new release using GitHub and upload the JAR file you created.
6.6. Managing Dependencies
A project often depends on third-party libraries. For example, card folder depends on the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
-
Include those libraries in the repo (this bloats the repo size)
-
Require developers to download those libraries manually (this creates extra work for developers)
Appendix A: Product Scope
Target user profile:
-
medicine students who need to rote memorisation of information
-
finds carrying physical flashcards around troublesome and prefers an application to help them store and organize their learning material
-
prefer desktop apps over other types
-
can type fast
-
prefers typing over mouse input
-
is reasonably comfortable using CLI apps
Value proposition: flashcards that are able to test the user instead of simply having them recall the answer. The user experience is more engaging and scoring is more accurate as it is based on actual performance rather than reported performance.
Appendix B: User Stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
student |
have flashcards with questions and answers |
have an easier time memorising content |
|
student |
create and delete my own flashcards |
|
|
student |
edit the content of my flashcards |
add on more content or correct any errors |
|
student |
have folders to store flashcards |
logically group flashcards of the same topic |
|
student |
navigate in and out of folders |
see one folder’s cards at each point of time |
|
student |
test myself on each flashcard folder |
better learn the content |
|
student |
attempt keying in answers before flashcards reveal them |
have a more engaging experience |
|
student |
view the answers of questions directly |
proceed even when I do not remember the answer |
|
student |
know how well I’ve been performing on each flashcard |
know my overall progress |
|
student |
view a progress report by folder |
know my performance for each topic |
|
student |
sort flashcards by score |
know which questions i have more trouble answering |
|
student |
import and export flashcards |
share content |
|
student |
search flash cards in a folder |
save time looking for a particular card |
|
student |
search folders |
save time looking for a particular folder |
|
student |
move flashcards from one folder to another |
better manage my flashcards |
|
student |
add hints that I can toggle on/off |
get help with more difficult cards |
|
student |
add pictures to certain flashcards |
better represent topics that heavily feature topics and diagrams |
|
student |
have a question that expects more than one answer |
test myself more complex questions |
|
student |
different template designs for my flashcards |
have a personalised experience while revising |
Appendix C: Use Cases
(For all use cases below, the System is Know-It-All
and the Actor is the Student
, unless specified otherwise)
UC01 Test flashcards
MSS
-
Student is inside a folder and begins a test session.
-
System presents the question on the lowest-performing flashcard first.
-
Student inputs his/her answer.
-
System indicates whether student’s answer is correct or wrong and shows the answer of the flashcard.
-
Student navigates to next flashcard.
-
Repeat steps 2-4 until all the flashcards in the folder are tested.
Use case ends
Extensions
-
3a. Student doesn’t know the answer and wants to see the answer without attempting.
-
3a1. Student uses the reveal command.
-
3a2. Answer is displayed to the student.
-
UC02 Add flashcards
MSS
-
Student navigates to a folder that he wants to add a flashcard to.
-
Student inputs question and answer to be stored as flashcard.
-
System stores the details as a flashcard under the current folder.
Use case ends
Extensions
-
2a. Student only inputs a question but no answer.
-
2a1. System displays an error message informing the user that the command format is invalid.
Use case resumes from step 2.
-
UC03 Edit flashcard question
MSS
-
Student navigates to the folder that contains the flashcard to be edited.
-
Student indicates the card to be edited, as well as the new question.
-
System stores the updated details for the edited card.
Use case ends
Extensions
-
2a. Student enters a blank as the desired question.
-
2a1. System displays an error message informing the user that the question cannot be a blank.
Use case resumes from step 2.
-
-
2b. Student enters a card index that does not exist.
-
2b1. System displays an error message prompting the user to choose a valid card index.
Use case resumes from step 2.
-
UC04 Add folder
Guarantees
-
A folder of the desired name is created.
MSS
-
Student navigates to home directory.
-
Student inputs the name of the folder he wants to create.
-
System creates a folder of the desired name and shows it on the home directory.
Use case ends
Extensions
-
2a. Student inputs a name that already exists.
-
2a1. System displays an error message prompting the user to use a folder name that is not taken.
Use case resumes from step 2.
-
UC05 Edit folder name
Guarantees
-
A particular folder as selected by the student is renamed to the desired name.
MSS
-
Student navigates to home directory.
-
Student indicates the folder he wants to rename, as well as the new name.
-
System renames the folder to the new name and shows it on the home directory.
Use case ends
Extensions
-
2a. Student inputs a name that already exists.
-
2a1. System displays an error message prompting the user to use a folder name that is not taken.
Use case resumes from step 2.
-
-
2b. Student chooses a folder that does not exist.
-
2b1. System displays an error message prompting the user to choose a valid folder.
Use case resumes from step 2.
-
-
2c. Student enters a blank as the desired new folder name.
-
2c1. System displays an error message informing that the folder name cannot be a blank.
Use case resumes from step 2.
-
UC06 Navigating into folders
MSS
-
Student indicates the folder he wants to enter.
-
System enters the folder and displays the folder content.
Use case ends
Extensions
-
1a. Student chooses a folder that does not exist.
-
1a1. System displays an error message prompting the user to choose a valid folder.
Use case resumes from step 1.
-
-
1b. Student is already inside a folder.
-
1b1. System displays an error message informing that the user can only navigate into the folder when he is at the home directory.
-
1b2. Student navigates back to home directory.
Use case resumes from step 1.
-
UC06 Display report for a folder
MSS
-
Student enters the a folder.
-
Student indicates that they want to see the report for this folder.
-
System displays a full-screen report.
Use case ends
Appendix D: Non Functional Requirements
-
Should work on any mainstream OS as long as it has Java
9
or higher installed. -
Should be able to hold up to 1000 cards without a noticeable sluggishness in performance for typical usage.
-
A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
-
The user interface should be intuitive enough even for medical students to use the app.
-
In the event where corrupted files are present, uncorrupted files should be preserved so that users do not lose their data.
-
When no data is present, sample data should be generated so users are able to use the app on launch.
-
Know-It-All works without active internet connection.
-
Know-It-All needs to be able to allow at least 50 characters for folder names and 256 characters for card information.
Appendix E: Glossary
Card Score: The number of correct answers divided by the number of attempts for a single card. When the user is tested on a card, this number is automatically calculated and recorded.
Flashcard/Card: An object containing a single question and answer, and optionally, hints. There are 2 types of cards, Single-answer and MCQ. MCQ cards feature incorrect options in addition to the card answer, while Single-answer cards do not.
Test score: The number of cards correctly answered over number of cards attempted during a test session. This number is automatically recorded after each test session.
Appendix F: Instructions for Manual Testing
Given below are instructions to test the app manually.
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing. |
F.1. Launch and Shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a sample folder. The window size may not be optimal.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
F.2. Card operations
-
Adding a single-answer card
-
Prerequisites: Enter a folder using the
cd
command. For example,cd 1
will enter the first folder. The folder may or may not be empty. -
Test case:
add q/What is the largest organ of the human body? a/Skin h/Anatomy
Expected: A new card with question "What is the largest organ of the human body?", answer "Skin" and hint "Anatomy" is created. Status message confirms this and the card list panel UI on the left updates to show the new card. -
Test case:
add a/Alexander Fleming q/Who discovered Penicillin?
Expected: A new card with question "Who discovered Penicillin?" and answer "Alexander Fleming" is created. Note that this card has no hint, as none was specified. Status message confirms this and UI is updated. -
Test case:
add q/ a/Some answer
Expected: The card is not created. Error details shown in the status message. -
Other incorrect
add
commands to try for single-answer cards:
add
,add some question
,add q/ a/ h/
,add q/Some question a/
.
Expected: Similar to previous.
-
-
Adding an MCQ card
-
Prerequisites: Enter a folder using the
cd
command. -
Test case:
add q/What does MRI stand for? a/Magnetic Resonance Imaging i/Medical Resource Image
Expected: A new card with question "What does MRI stand for?", answer "Magnetic Resonance Imaging" and incorrect option "Medical Resource Image" is created. Status message confirms this and UI is updated. -
Test case: (After executing step 1c)
add a/Alexander Fleming q/Who discovered Penicillin? i/Hippocrates
Expected: The card is not created. Error details shown in the status message. -
Other incorrect
add
commands to try for MCQ cards:
add q/ a/ i/ i/
,add q/Some question a/Some answer, i/
,add q/question a/answer i/answer
Expected: Similar to previous.
-
-
Editing a card
-
Prerequisites: Enter a folder using the
cd
command. Ensure that cards already exist in the folder. For example,select 1
should execute and display a card in right-side panel. -
Test case:
edit 1 a/Sigmund Freud h/Psychology
Expected: First card in the folder is edited to have answer "Sigmund Freud" and hint "Psychology". -
Test case:
edit 1 h/
Expected: First card in the folder is edited to have no hint. -
Test case:
edit 1 i/Hippocrates i/Carl Jung
Expected: First card in the folder is edited to have incorrect options "Hippocrates", "Carl Jung", and is converted to an MCQ card if it was originally a single-answer card. -
Test case:
edit 1 i/
Expected: First card in the folder is edited to have no incorrect options, and is converted back to a single-answer card. -
Test case:
edit 1 q/
Expected: The card is not edited. Error details shown in the status message. -
Other incorrect
edit
commands to try:edit x
,edit
,edit 1 q/ a/
Expected: Similar to previous.
-
-
Deleting a card
-
Prerequisites: Enter a folder using the
cd
command. Ensure that cards already exist in the folder. -
Test case:
delete 1
Expected: First card in the folder is deleted. -
Test case:
delete
Expected: No card is deleted. Error details shown in the status message. -
Other incorrect
delete
commands to try:delete 0
,delete this
Expected: Similar to previous.
-
F.3. Starting and ending a test session
-
Start a test session while inside a folder
-
Prerequisites: Enter a folder using the
cd
command. For example,cd 1
will enter the first folder. All the cards in the folder are listed. -
Test case:
test
Expected: Enters a test session, where the display area enters a fullscreen and the lowest scoring flashcard question and hints (if any) will be displayed first.In Test Session
is shown on the status message and status bar. -
Test case:
test
on an empty folder (this folder has no cards)
Expected: The errorThis command is not valid on an empty folder
is shown in the status message. Status bar remains the same (still in folder). -
Test case:
test
when not inside a folder (prerequisite not fulfilled, like being in the home directory, already in a test session or in report display)
Expected: The errorCommand can only be executed in folder
is shown in the status message. Status bar remains the same.
-
-
Ends a current test session
-
Prerequisites: Enter a test session by typing
test
when inside a folder. Display area enters fullscreen and lowest scoring flashcard question and hints are displayed. Tester can also be in any state in the test session -
Test case:
end
Expected: Ends the current test session, where the display area exits fullscreen and be back inside the folder.End Test Session
is shown on the status message and status bar shows that tester is back inside the folder. -
Test case:
end
when not inside a test or report (prerequisite not fulfilled, like being in the home directory or in a folder)
Expected: The errorThis command is not valid outside a test or report
is shown in the status message. Status bar remains the same.
-
F.4. Answering a question in a test session
-
Input an answer to a question inside a test session
-
Prerequisites: Enter a test session by typing
test
when inside a folder. Display area enters fullscreen and lowest scoring flashcard question and hints are displayed -
Test case:
ans x
where x can be anything but empty for non-MCQ cards
Expected: If the correct answer to the flashcard question isx
, a green page with correct attempt notification will be displayed. Otherwise, a red page with wrong answer notification will be displayed. For both cases, status bar remains the same to be still in test session -
Test case:
ans x
where x can be any of the option number displayed for MCQ cards
Expected: If the option number with the correct answer to the flashcard question isx
, a green page with correct attempt notification will be displayed. Otherwise, a red page with wrong answer notification will be displayed. For both cases, status bar remains the same to be still in test session -
Test case:
ans x
where x is not an option number and not empty for MCQ cards
Expected: The errorMCQ question expects option number as answer
is shown in the status message. Status bar remains the same. -
Test case:
ans
Expected: The errorInvalid command format
is shown in the status message. Status bar remains the same. -
Test case:
ans x
and typeans y
again where x and y can be anything but empty for non-MCQ cards
Expected: Cannot input an attempt to an already answered question. The errorAnswer command is valid only when a question is displayed
is shown in the status message. Status bar remains the same.
-
F.5. Navigating to the next question in a test session
-
Go to the next question in a test session after answering the current question
-
Prerequisites: Enter a test session by typing
test
when inside a folder. Display area enters fullscreen and lowest scoring flashcard question and hints are displayed. Answer the current question by following Section F.4, “Answering a question in a test session” or reveal the answer so the current card has already been tested. -
Test case:
next
where there are still cards left untested
Expected: Next lowest scoring question and hints (if any) are displayed. Status bar remains the same to be still in test session. -
Test case:
next
where there are no more cards left to test
Expected: Ends the current test session, so display area exits fullscreen and status bar changes to reflect tester is back inside a folder. -
Test case:
next
when prerequisite is not fulfilled where the current question is not yet answered or revealed
Expected: The errorNext command is valid only when this question has been answered
is shown in the status message. Status bar remains the same.
-
F.6. Viewing a report display
-
View report display
-
Prerequisites: Enter a folder. The tester may want to ensure that they have at least two test attempts on this folder, as a line graph requires at least two points to be drawn. (If there are fewer than two attempts, it is not a bug if no line is drawn.)
-
Test case:
report
Expected: Enters report display, where the display area enters fullscreen. A graph is displayed showing maximum of last ten test scores for this folder. A list, maximum of the three lowest scoring questions, is displayed.Report displayed
is shown on the status message and status bar shows that tester is in report display. -
Test case: Other invalid commands such
test
,add
, or a secondreport
command.
Expected: They should not be allowed. An error message will be displayed. -
Test case:
end
Expected: Ends the current report session, where the display area exits fullscreen and be back inside the folder.End Report Session
is shown on the status message and status bar shows that tester is back inside the folder.
-
F.7. Sorting the cards
-
Sort
-
Prerequisites: Enter a folder. The tester may want to ensure that they have at least two cards in this folder, or else there is no way to tell if the sort happened.
-
Test case:
sort
Expected: Sorts cards in non-descending percentage score. If card A is above card B, card A will not have a higher percentage score than card B.Sorted flashcards with lowest score first
is shown on the status message.
-
F.8. Folder operations
-
Adding a folder
-
Prerequisites: You must be at the home directory (status bar should read
In Home Directory
) and have only one folder called "Sample Folder". To get this configuration, you can delete all data files except for the jar file and relaunch the application. -
Test case:
addfolder Folder 1
Expected: A folder by the name of "Folder 1" is added. Status message confirms that the folder added, and the new folder can be found in the list of folders. -
Test case:
addfolder Sample Folder
Expected: No folder is added. Error details shown in the status message. -
Other incorrect addfolder commands to try:
addfolder
,addfolder Special/Chars
,addfolder x
(where x is string longer than 50 characters)
Expected: Similar to previous.
-
-
Deleting a folder
-
Prerequisites: You must be at the home directory (status bar should read
In Home Directory
) and have at least one folder. -
Test case:
deletefolder 1
Expected: The folder at index 1 is deleted. Status message confirms that the folder is deleted, and the folder can no longer be found in the list of folders. -
Test case:
deletefolder 0
Expected: No folder is deleted. Error details shown in the status message. -
Other incorrect deletefolder commands to try:
deletefolder
,deletefolder Sample Folder
,deletefolder x
(where x is larger than the list length)
Expected: Similar to previous.
-
-
Renaming a folder
-
Prerequisites: You must be at the home directory (status bar should read
In Home Directory
) and have at least one folder called "Sample Folder", and no folder called "New Name". -
Test case:
editfolder x New Name
(where x is the index of a folder that is not Sample Folder)
Expected: The folder at index x is renamed to "New Name". Status message confirms that renaming operation succeeded. -
Test case:
editfolder x sample folder
(where x is the index of Sample Folder)
Expected: The folder at index x is renamed to its original name (with new capitalisation). Status message confirms that renaming operation succeeded. -
Test case:
editfolder x Sample Folder
(where x is not the index of Sample Folder)
Expected: The folder at index x is not renamed. Error details shown in the status message. -
Other incorrect editfolder commands to try:
editfolder
,editfolder New Name 1
,editfolder x New Name
(where x is larger than the list length)
Expected: No folder is renamed. Error details shown in the status message.
-
-
Persistence of folder operations
-
Prerequisites: You must be at the home directory (status bar should read
In Home Directory
) and know where the data files are stored. By default, this will be at thedata/
directory at the path of the jar file. -
Test case:
addfolder x
(where x is the name not used by any existing folder)
Expected: A new json by the name ofx.json
appears in the data directory. -
Test case:
deletefolder 1
Expected: The json with the same file name as the deleted folder is no longer present in the data directory. No other files in the directory are affected. -
Test case:
editfolder 1 x
(where x is a name not used by any existing folder)
Expected: The json with the original name of the edited folder is no longer present in the data directory, replaced with a json with the new folder name. No other files in the directory are affected.
-
F.9. Navigating in and out of folders
-
Entering a folder
-
Prerequisites: You must be at the home directory (status bar should read
In Home Directory
) and have at least one folder. -
Test case:
cd 1
Expected: Folder 1 is entered. Status message confirms this and UI is updated. -
Test case:
cd x
(where x is larger than the list length)
Expected: No folder is entered. Error details shown in the status message. -
Other incorrect cd commands to try:
cd
,cd Sample Folder
,cd ..
Expected: Similar to previous.
-
-
Exiting a folder
-
Prerequisites: You must be inside a folder (status bar should read
Inside Folder: x
wherex
is the name of the folder you are in). -
Test case:
cd ..
Expected: The folder is exited and you return to the home directory. Status message confirms this and UI is updated. -
Test case:
cd x
(where x is any combination of characters other than "..")
Expected: The folder is not exited. Error details shown in the status message. -
Other incorrect cd commands to try:
cd
,cd Home
Expected: Similar to previous.
-
F.10. Saving data
-
Dealing with missing/corrupted data files
-
Prerequisites: You must know where the data files are stored. By default, this will be at the
data/
directory at the path of the jar file. -
Test case: Delete the data directory and launch the app
Expected: A sample folder is present when the app launches (although it is not committed to storage until a persistent change is made). -
Test case: Insert a non-json file/corrupted json file in the data directory and launch the app
Expected: The valid json files have folders with their corresponding names present. The non-json file/corrupted json file remains unaffected.
-
-
Importing a folder
-
Prerequisites:
You must be outside a folder (status bar should readin home directory
.
Ensure that you also have the file to import in the project root directory.
file has to have a .csv extension format.
The imported file has to comply with the csv file headers.
Compulsory fields, like Question and Answer should not be left blank.
folder should not already exist within the model. -
Test case:
import Blood.csv
Prerequisites: Ensure that Blood.csv exists within the project root directory.
Expected: Command box outputs a message "Successfully imported: Blood.csv" and a new folder with name Blood is added to the existing folders in the home directory, with the corresponding flashcards added to the folder -
Other incorrect commands to try:
import
,import Blood.json
-
-
Exporting a folder
-
Prerequisites: You must be outside a folder (status bar should read
in home directory
)
flashcard application -
Test case:
export 1
Prerequisites: You must have at least one folder existing in the application.
Expected: The command box outputs a message "Successfully exported card folders" and project root directory should contain ax.csv
file wherex
is the name of the first folder in the application. -
Test case:
export 1 2 3
Prerequisites: You must have at least 3 card folders existing in the application.
Expected: The same message output in the command box as the previous test case and the corresponding filesx1.csv
,x2.csv
andx3.csv
created in project root directory, wherex1
,x2
,x3
corresponds to the name of the first, second and third folder -
Other incorrect commands to try:
export
,export -1
,export 12 13 14
, where12
13
and14
are non existent card folder indexes.
-