By: Team SE-EDU      Since: Jun 2016      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9 or later

    JDK 10 on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK 9.
  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

1.2. Setting up the project in your computer

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

  9. Open XmlAdaptedWish.java and MainWindow.java and check for any code errors

    1. 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

    2. 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

  10. Repeat this for the test folder as well (e.g. check XmlUtilTest.java and HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

  1. Run the seedu.url.MainApp and try a few commands

  2. 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,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

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-AY1819S1-T16-1/main repo.

If you plan to develop this fork as a separate product (i.e. instead of contributing to CS2103-AY1819S1-T16-1/main), you should do the following:

  1. Configure the site-wide documentation settings in build.gradle, such as the site-name, to suit your own project.

  2. Replace the URL in the attribute repoURL in DeveloperGuide.adoc and 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,

  1. Get some sense of the overall design by reading Section 2.1, “Architecture”.

  2. Take a look at [GetStartedProgramming].

2. Design

2.1. Architecture

Architecture
Figure 1. Architecture Diagram

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 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. Two of those classes play important roles at the architecture level.

  • EventsCenter : This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design)

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

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.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

Events-Driven nature of the design

The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1.

SDforDeleteWish
Figure 3. Component interactions for delete 1 command (part 1)
Note how the Model simply raises a WishBookChangedEvent when the Wish Book data is changed, instead of asking the Storage to save the updates to the hard disk. This event also triggers the save of wish histories to disk.

The diagram below shows how the EventsCenter reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.

SDforDeleteWishEventHandling
Figure 4. Component interactions for delete 1 command (part 2)
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 5. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, WishListPanel, StatusBarFooter, BrowserPanel etc. All these, 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 that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model change.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

2.3. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

  1. Logic uses the WishBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a wish) and/or raise events.

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

DeletePersonSdForLogic
Figure 7. Interactions Inside the Logic Component for the delete 1 Command

2.4. Model component

ModelClassDiagram
Figure 8. Structure of the Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Wish Book data.

  • stores the data of wish histories.

  • exposes an unmodifiable ObservableList<Wish> 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. The elements of the ObservableList<Wish> can also be filtered and sorted to suit the needs of specific commands.

  • does not depend on any of the other three components.

As a more OOP model, we can store a Tag list in Wish Book, which Wish can reference. This would allow Wish Book to only require one Tag object per unique Tag, instead of each Wish needing their own Tag object. An example of how such a model may look like is given below.

ModelClassBetterOopDiagram

2.5. Storage component

StorageClassDiagram
Figure 9. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the Wish Book data in xml format and read it back.

2.6. Common classes

Classes used by multiple components are in the seedu.WishBook.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. Model

3.1.1. Wish Model

A wish is uniquely identified by its Universal Unique Identifier (UUID) which is generated randomly only once for a particular wish, upon its creation through the AddCommand. A wish stores the following primary attributes:

  • Name

  • Price

  • Date

  • Saved Amount

  • Url

  • Remark

  • Tags

  • UUID

It is impossible for the user to create a duplicate wish as it is impossible to modify a wish’s UUID.

3.1.2. Wish Priority

A wish needs to be prioritised in a specific order such that the wishes with the highest priority will be visible on the top of the list. In WishBook, the priority is determined primarily by the due date of the wish which is stored in every wish’s Date attribute. Ties are broken by Name. Further ties are broken by UUID as it is possible for the Date and Name of two wishes to be identical.

The sorting of the displayed results is done by the filteredSortedWishes list. The sorting order is specified by WishComparator.

3.1.3. Design Considerations

Aspect: Uniqueness of a Wish
  • Alternative 1(current choice): Identify a Wish by a randomly generated UUID.

    • Pros: Extremely low probability of collision.

    • Pros: No extra maintenance required upon generation as every Wish is unique.

    • Cons: UUID does not map to any real world entity and it is used strictly for identification.

    • Cons: It is more difficult to system test the AddCommand with the current group of methods for system tests as UUID is randomly generated each time.

  • Alternative 2: Identify a wish by Name, Price, Date, Url, Tags. Wishes with identical values for these attributes will be represented by a single WishCard. The WishCard will be augmented with a Multiplicity to indicate the number of identical wishes.

    • Pros: WishBook will be more compact and every attribute stored in a Wish maps to a real entity.

    • Cons: Additional attribute Multiplicity may have to be frequently edited as it is another attribute that is affected by multiple commands.

  • Alternative 3: Identify a wish by a new attribute CreatedTime, which is derived from the system time when the wish is created.

    • Pros: The attribute maps to a real entity. It can be an additional information presented to the user about a wish.

    • Cons: There might be collisions in CreatedTime if the the system time is incorrect.

3.2. Undo/Redo feature

3.2.1. Current Implementation

The undo/redo mechanism is facilitated by VersionedWishBook. It extends WishBook with an undo/redo history, stored internally as an WishBookStateList and currentStatePointer. Additionally, it implements the following operations:

  • VersionedWishBook#commit() — Saves the current wish book state in its history.

  • VersionedWishBook#undo() — Restores the previous wish book state from its history.

  • VersionedWishBook#redo() — Restores a previously undone wish book state from its history.

These operations are exposed in the Model interface as Model#commitWishBook(), Model#undoWishBook() and Model#redoWishBook() 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 VersionedWishBook will be initialized with the initial wish book state, and the currentStatePointer pointing to that single url book state.

UndoRedoStartingStateListDiagram

Step 2. The user executes delete 5 command to delete the 5th wish in the wish book. The delete command calls Model#commitWishBook(), causing the modified state of the url book after the delete 5 command executes to be saved in the WishBookStateList, and the currentStatePointer is shifted to the newly inserted url book state.

UndoRedoNewCommand1StateListDiagram

Step 3. The user executes add n/David …​ to add a new wish. The add command also calls Model#commitWishBook(), causing another modified wish book state to be saved into the WishBookStateList.

UndoRedoNewCommand2StateListDiagram
If a command fails its execution, it will not call Model#commitWishBook(), so the wish book state will not be saved into the WishBookStateList.

Step 4. The user now decides that adding the wish was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoWishBook(), which will shift the currentStatePointer once to the left, pointing it to the previous wish book state, and restores the url book to that state.

UndoRedoExecuteUndoStateListDiagram
If the currentStatePointer is at index 0, pointing to the initial wish book state, then there are no previous url book states to restore. The undo command uses Model#canUndoWishBook() 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:

UndoRedoSequenceDiagram

The redo command does the opposite — it calls Model#redoWishBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the wish book to that state.

If the currentStatePointer is at index WishBookStateList.size() - 1, pointing to the latest wish book state, then there are no undone url book states to restore. The redo command uses Model#canRedoWishBook() 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 wish book, such as list, will usually not call Model#commitWishBook(), Model#undoWishBook() or Model#redoWishBook(). Thus, the WishBookStateList remains unchanged.

UndoRedoNewCommand3StateListDiagram

Step 6. The user executes clear, which calls Model#commitWishBook(). Since the currentStatePointer is not pointing at the end of the WishBookStateList, all wish book states after the currentStatePointer will be purged. We designed it this way because it no longer makes sense to redo the add n/David …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoNewCommand4StateListDiagram

The following activity diagram summarizes what happens when a user executes a new command:

UndoRedoActivityDiagram

3.2.2. Design Considerations

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire wish book.

    • 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 wish 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 wish book 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 and VersionedWishBook.

  • 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.3. Savings History feature

3.3.1. Capturing the state of WishTransaction

The current state of the savings history of the WishBook is captured by VersionedWishTransaction. VersionedWishTransaction extends WishTransaction and has an undo/redo history, similar to the implementation of the Undo/Redo feature, and is stored internally as a wishStateList and currentStatePointer. Additionally, it implements VersionedModel and so contains the implementation of the following operations:

  • VersionedWishTransaction#commit() — Saves the current wish transaction state in its history.

  • VersionedWishTransaction#undo() — Restores the previous wish transaction state from its history.

  • VersionedWishTransaction#redo() — Restores a previously undone wish transaction state from its history.

These operations are exposed in the Model interface as Model#commitWishBook(), Model#undoWishBook() and Model#redoWishBook() respectively.

3.3.2. Capturing the state of each Wish

WishTransaction keeps track of the state of all wishes in WishBook via a wishMap which maps the unique ID of a Wish to a list of Wish states. WishTransaction implements ActionCommandListener such that any state changing command performed to a Wish or the WishBook such as AddCommand(), EditCommand(), SaveCommand(), etc will result in the WishMap being updated accordingly in WishTransaction.

3.3.3. Persistent storage

VersionedWishTransaction, WishTransaction can be easily converted to and from xml using XmlWishTransactions. XmlWishTransactions is saved as an xml file when the user explicitly closes the window, thereby invoking MainApp#stop() which saves the current state of VersionedWishTransaction in the wishStateList to hard disk.

If the user’s command triggers a change in the state of the WishBook, a WishBookChangedEvent will be raised, causing the subscribed StorageManager to respond by saving both the current state of the WishBook and WishTransaction to disk.

Given below is an example usage scenario and how the savings history mechanism behaves at each step.

Step 1. The user launches the application. The default file path storing the previous state of the WishTransaction will be retrieved, unless otherwise specified by the user, and the contents from the xml file will be parsed and converted into a WishTransaction object via the XmlWishTransactions object. If the file at the specified location is behind the current state of the WishBook, content of the WishTransaction will be overwritten by the WishBook.

The wishStateList starts off with the initial state of the WishTransaction as the first item in the list.

Step 2. The user executes add n/iPhone …​ to add a new wish. The add command calls Model#commitWishBook(), causing the current state of the modified wish transaction state to be saved into wishStateList. As this is a command that changes the state of the WishBook, Model#addWish() will call VersionedWishTransaction#addWish() to add a new wish to the WishMap.

  • If a command fails its execution, it will not call Model#commitWishBook(), so the wish transaction state will not be saved into the wishStateList.

  • If the WishMap contains an identical wish (such is identified by Wish#isSameWish()), then the call to add this wish will fail. As such, the wish will not be added to the WishMap or the WishBook.

Step 3. The user now decides that adding the wish was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoWishBook(), which will shift the currentStatePointer once to the left, pointing it to the previous wish transaction state, and restores the wish transaction to that state.

If the currentStatePointer is at index 0, pointing to the initial wish transaction state, then there are no previous wish transaction states to restore. The undo command uses Model#canUndoWishBook() 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 redo command does the opposite — it calls Model#redoWishBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the wish transaction to that state.

If the currentStatePointer is at index wishStateList.size() - 1, pointing to the latest wish transaction state, then there are no undone wish transaction states to restore. The redo command uses Model#canRedoWishBook() 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 4. The user then decides to execute the command list. Commands that do not modify the state of the WishBook, such as list, will usually not call Model#commitWishBook(), Model#undoWishBook() or Model#redoWishBook(). Thus, the WishBookStateList remains unchanged.

Step 5. The user finally exits the app by clicking on the close button. The most recent state of the WishTransaction will be converted into xml format via the the XmlWishTransactions object and be saved into the same file path it was first retrieved from.

If there was some error saving the current state of the WishTransaction to the specified file path in hard disk, an exception will be thrown and a warning will be shown to the user. The current state of the WishTransaction object will not be saved to hard disk.

3.4. Add Wish feature

3.4.1. Current Implementation

The Add Wish feature is executed through an AddCommand by the user, which after parsing, is facilitated mainly by the ModelManager which implements Model. It also affects versionedWishBook and versionedWishTransaction by adding the resultant wish to both of their respective data structures. After adding a Wish, the filteredSortedWishes is also updated to reflect the latest version of WishBook. The UI is also prompted to refresh through a WishBookChangedEvent.

AddCommandParser parses the user’s input for parameters using prefixes, and checks them against their respective regular expressions (regex), specified in their respective classes.

The following prefix/parameter pairs are compulsory, where a user’s input will be rejected if they are not provided:

  • n/: Name

  • p/: Price

  • One of the following Date parameters:

    • d/: Exact expiry date

    • a/: duration (or lifetime) from time when command is entered

The following prefix/parameter pairs are optional, where a user’s input will be successful even if they are not provided:

  • t/: tags (more than one allowed)

  • u/: product’s URL (product page)

Regarding Duration (a/) vs Date (d/)

  • If d/ is used, a valid Date should be used.

    • Date comes in the format of dd/mm/yyyy, dd being days, mm being months, yyyy being the year, and the

    • Specified date should also be a valid date in the future.

  • If a/ is used, a valid Duration should be used.

    • length instead of dd/mm/yyyy format, the format should be <years>y<months>m<days>d.

  • In any command, only Duration or Date can be used. Never both.

  • If an invalid string for date or duration is provided, a warning will be displayed to prompt the user to enter a valid date or duration.

Given below is an example usage scenario and how an AddCommand is carried out.

Step 1. The user types in a valid AddCommand, add n/1 TB Toshiba SSD p/158 a/200d, and the current date is 2nd October 2017 (2/10/2017).

The AddCommandParser will employ ParserUtil to parse the attributes specified after each prefix. The parsing of the Duration attribute which follows a/ in the command will be discussed below.

Since Duration prefix is used, the computation of a wish’s expiry date is handled internally in the ParserUtil class, which ParserUtil#parseDate() parses and converts the input string into a Period object (if input is valid), and adds the resultant Period to the current Date to get the desired Date of the Wish.

The resultant Wish will have the following properties:

  • id: a randomly-generated UUID

  • Name: 1TB Toshiba SSD

  • SavedAmount: 0.00

  • Price: 158.00

  • Date: 20/4/2018 (20th April 2018)

  • URL: empty string

  • Remark: empty string

  • Tags: none

  • Fulfilled: false

  • Expired: false

The resultant wish is pass into VersionedWishBook#addWish and VersionedWishTransaction#addWish, which tracks the history of the WishBook and Wish respectively. The list of wishes shown on the UI is also updated to show all wishes again, as filteredSortedWishes is updated to have all wishes in WishBook and a WishBookChangedEvent is fired.

The following sequence diagram shows how an AddCommand is processed in WB:

AddWishSequenceDiagram

Step 2. Some time later, the user decides that she wants the exact same wish, but duplicated, and enters the exact same command, but with an exact Date instead of Duration, so the command entered is add n/1 TB Toshiba SSD p/158 d/20/4/2018.

Since Date prefix is used, the ParserUtil parses the string into a Date object, and the resultant object is used directly for the resultant Wish.

Similar to in Step 1, the command will be parsed successfully and a second Wish will be added, albeit with a different (hidden) id generated.

The resultant Wish will have the following properties:

  • id: another randomly-generated UUID

  • Name: 1TB Toshiba SSD

  • SavedAmount: 0.00

  • Price: 158.00

  • Date: 20/4/2018 (20th April 2018)

  • URL: empty string

  • Remark: empty string

  • Tags: none

  • Fulfilled: false

  • Expired: false

3.4.2. Design Considerations

  • Alternative 1 (current choice): Different prefixes for Duration and Date.

    • Pros: More focused user experience. User get more specific feedback depending on their preferred way of inputting date if a wrong input was made. If user uses a/ and enters an incorrect Duration, the user will not receive an error message about the correct format for an exact Date, and will only be notified of the correct format of a Duration.

    • Pros: Easier to implement and handle isolate errors related to respective input parameters.

    • Cons: More prefixes for user to remember.

  • Alternative 2: Have Duration and Date use the same prefix.

    • Pros: More natural usage of one prefix to determine Wish 's desired expiry date.

    • Cons: Conflating implementation of Duration and Date, hence harder to debug.

    • Cons: Tricky to implement, as we are parsing one input for two different desired formats.

3.5. Save Amount feature

3.5.1. Current Implementation

The Save Amount feature is executed through a SaveCommand by the user, which after parsing, is facilitated mainly by the ModelManager which implements Model. Wish stores the price and savedAmount of Wish, helping to track the progress of the savings towards the price. Meanwhile, WishBook stores an unusedFunds, which is an unallocated pool of funds that can be used in the future. After adding a saving, the filteredSortedWishes in ModelManager is updated to reflect the latest observable WishBook.

Given below is an example usage scenario and how the SaveCommand behaves at each step:

Step 1. The user executes save 1 10, to save $10 into an existing wish with Index 1 and Price $15. The $10 is wrapped in an Amount and a SaveCommand instance is created with the Amount. Amount is then used to make an updated instance of the Wish at index 1 whose SavedAmount will be updated. Model#updateWish is then called to update this wish with the old one in WishBook.

The Index of each Wish is labelled at the side of the app.

The resultant wish will have the following properties:

  • Name: 1TB Toshiba SSD

  • SavedAmount: 10.00

  • Price: 15.00

  • Date: 20/4/2018 (20th April 2018)

  • URL: empty string

  • Remark: empty string

  • Tags: none

  • Fulfilled: false

  • Expired: false

Amount to be saved can be a negative value where it would mean a withdrawal of money from a particular wish.
SavedAmount of a wish cannot be negative. This means that an Amount cannot be negative enough to cause SavedAmount to be negative.

Step 2. The user decides to execute save 1 10 again. However, SaveCommand checks that savedAmount > price. SaveCommand#execute creates a new updated Wish with savedAmount = wish.getPrice().

The resultant wish will have the following properties:

  • Name: 1TB Toshiba SSD

  • SavedAmount: 15.00

  • Price: 15.00

  • Date: 20/4/2018 (20th April 2018)

  • URL: empty string

  • Remark: empty string

  • Tags: none

  • Fulfilled: true

  • Expired: false

Step 3. The excess amount of $5 is stored in a new Amount variable excess. SaveCommand#execute then calls Model#updateUnusedFunds(excess) to update the unusedFunds in WishBook.

In WishBook, the result would be:

  • unusedFunds: 5.00

Step 4. The user tries to execute save 1 10 again. However, since the value for Wish#isFulfilled is true, the amount will not be saved. SaveCommand#execute will throw a CommandException, with the message "Wish has already been fulfilled!".

The following sequence diagram shows how the save operation works:

SaveCommandSequenceDiagram

3.5.2. Design Considerations

Aspect: Data structure to support the unusedFunds feature
  • Alternative 1 (current choice): Store it in a SavedAmount variable in WishBook.

    • Pros: Easy to implement.

    • Cons: More methods needed when needing to move funds from unusedFunds to other wishes.

  • Alternative 2: Store it as a pseudo wish with index 0.

    • Pros: It can be treated as another wish, hence existing methods can be used without needing to create much more new ones.

    • Cons: Requires dealing with an extra wish that has to be hidden on the WishListPanel and displayed separately on the UI. We must remember to skip this wish in methods that involve displaying the WishList.

3.6. Find Wish Feature

3.6.1. Current Implementation

The find mechanism is supported by FindCommandParser. It implements Parser that implements the following operation:

  • FindCommandParser#parse() — Checks the arguments for empty strings and throws a ParseException if empty string is found. It then splits the arguments using ArgumentTokenizer#tokenize() and returns an ArgumentMultimap. Keywords of the same prefix are then grouped using ArgumentMultimap#getAllValues().

The find mechanism is also facilitated by FindCommand. It extends Command and implements the following operation:

  • FindCommand#execute() — Executes the command by updating the current FilteredSortedWishList with the WishContainsKeywordPredicate.

The predicate WishContainsKeywordsPredicate, takes in three lists of the keywords for the following attributes:

  • Name

  • Tags

  • Remark

and also the isExactMatch argument. The result of the predicate is determined by checking whether a Wish contains the given keywords at their corresponding attributes. The match threshold is dictated by the value of isExactMatch.

3.6.2. Example

Given below is an example usage scenario and how the Find mechanism behaves at each step.

Step 1. The user launches the application for the first time.

Step 2. The user executes find n/wat n/apple t/impor command to get all wishes whose name contains the keywords 'iphone' or 'tablet'.

Step 3. The FindCommandParser#parse() is called and the WishContainsKeywordPredicate is constructed with the arguments of the find command.

Step 4. FindCommand#execute() is then called.

Step 5. The entire list of wishes is filtered by the predicate WishContainsKeywordsPredicate.

Step 6. The filtered list of wishes is returned to the GUI.

FindCommandSequenceDiagram

3.6.3. Design Considerations

Aspect: Argument format
  • Alternative 1 (Current choice): Require the user to prepend every keyword argument with the appropriate Wish attribute prefix.

    • Pros: Easier to implement as it easier to match keyword against a Wish if the attribute to match against is known.

    • Pros: User has more control over the results returned.

    • Cons: User is required to type slightly more.

  • Alternative 2: No prefixes are required in the arguments. Keywords can match with any one of the following chosen wish attributes: Name, Tags or Remark.

    • Pros: Less typing required from user.

    • Cons: Command might be slightly slower as every keyword has to be checked against all chosen attributes of the wish.

    • Cons: User has less control over the results returned.

3.6.4. Aspect: Default threshold for match without the exact match flag

  • Alternative 1 (Current choice): Keywords appended to different prefixes are grouped with a logical AND and keywords appended to the same prefixes are grouped with a logical OR when being matched against a Wish.

    • Pros: A more intuitive way to find wishes.

    • Cons: Can be restrictive in some situations.

  • Alternative 2: Keywords appended to different prefixes are grouped with a logical OR and keywords appended to the same prefixes are grouped with a logical OR when being matched against a Wish.

    • Pros: Search results will be more inclusive.

    • Cons: Very slim chance for such a use case.

3.7. List feature

The list -c and list -u command allows the user to view the list of all wishes, completed and ongoing, respectively. A wish is completed if the savedAmount is greater or equal to the price of the wish.

3.7.1. Current Implementation

Given below is an example usage scenario and how the list overdue mechanism behaves at each step:

  1. The user executes the command list -c.

  2. model.updateFilteredWishList() will update the wish list with WishCompletedPredicate as the parameter (boolean). wish.isFulfilled() is called to check whether the wish is completed or not.

  3. The updated wish list would be reflected on the UI to be displayed to the user.

The following sequence diagram shows how the Wish Detail Panel displays its updated content:

ListCompletedSequenceDiagram

3.8. Redesign of User Interface

The UI has been redesigned to implement the following UI components required for WishBook:

  • Command Box

  • Wish List Panel

  • Wish Detail Panel

3.8.1. Wish List Panel

The Wish List Panel consists of a list of Wish Card which contains 4 UI elements:

  • WishCard#nameLabel - A Text element that displays the wish’s name.

  • WishCard#progressLabel - A Text element that displays the wish’s saving progress in percentage format.

  • WishCard#tags - A FlowPane element that contains a Text element which displays the wish’s assigned tags.

  • WishCard#progressBar - A progressBar element that visually presents the percentage of the wish’s current saving progress.

Whenever the user adds a new wish or edits an existing wish, a new WishCard containing the wish will be added to the Wish List Panel or the content in the existing WishCard will be updated respectively.

The user will be able to view the wish’s current saving progress both in terms of text on the progressLabel (e.g. ’80%’) and the progressBar. Also, the user will be able to see all the tags he/she assigned to categorize the wish.

Problem with the old design

The UI (MainWindow) constructs the WishListPanel by obtaining an ObservableList of wish cards from Model, this list is assigned when UI starts, and will never be re-assigned. The UI "observes" the list and updates when it is modified.

This approach works well for the WishListPanel because WishBook contains only 1 list of wish cards. However, the saving history list in the WishDetailPanel can not be updated in the same manner because Model component will change its card list’s reference when a user adds a new wish or updates the content of the wish.

In this case, the WishDetailPanel in UI will not be updated because the card list of which UI has reference to is actually not changed.

Design considerations
  • Alternative 1 (current choice): Have a wishList in Model and keep it updated with the current list of cards

    • Explanation: The UI needs only 1 reference to this wishList, each time user executes any changes, wishList is cleared and the new list of cards is copy to the wishList.

    • Pros: The structure of Model and UI component needs not be changed

    • Cons: Need to keep a copy of the current card list, copying the whole list of cards for each command operation has bad effect on performance
.

  • Alternative 2: Model component raises an event when its current card list’s reference is changed

    • Explanation: When user adds a new wish or executes save, Model will raise an event (WishPanelUpdatedEvent), which is subscribed by UI, then UI can re-assign its list of cards and update the cards panel accordingly.

    • Pros: Better performance

    • Cons: Need to re-design Model and UI components


3.8.2. Wish Detail Panel

The Wish Detail Panel consists of 3 UI sub-components:

  • WishDetailSavingAmount that contains Text elements to display price and the saving progress of the wish

  • WishDetailSavingHistory that contains a List of history of saving inputs of the wish

  • WishBrowserPanel that displays WebView of the URL of the wish.

Whenever the user adds a new wish or edits an existing wish, the content of the wish in Wish Detail Panel will be updated. The user will be able to view the wish’s current saving progress and the history of his/her saving inputs of the wish in the list format. Also, the user will be able to browse through the wish’s product page via its assigned URL.

Current Implementation

Every time a new Wish is added or an existing wish is updated by the commands such as save, it raises a WishDataUpdatedEvent. The UI will then handle that event and update the WishDetailPanel with the new version of wish.

Given below is an example usage scenario and how the WishBook behaves and WishDetailPanel is updated at each step:

  1. The user executes the command save 1 1000.

If a command fails its execution, WishDataUpdatedEvent will not be posted.
  1. The save command updates the model with the new wish and raises a new WishDataUpdatedEvent.

  2. WishDetailSavingAmount and WishDetailSavingHistory responds to the WishDataUpdatedEvent with WishListPanel#handleWishUpdatedEvent().

  3. The WishDetailSavingAmount updates the wish’s current saving progress when WishDetailSavingAmount#loadWishDetails is called.

  4. The progress is calculated from when Wish#getProgress is called. The value is saveAmount / price. Then the progress label for the wish is set to that fraction.

  5. The WishDetailSavingHistory updates the wish’s saving history list when WishDetailSavingHistory#loadWishDetails is called.

  6. The saving history list is cleared.

  7. The new set of history entry is retrieved from wishTransaction#getWishMap and the saved amount is calculated from subtracting previous saving amount from the next one.

  8. The saving history list is now filled with the new list of updated saving history.

The following sequence diagram shows how the Wish Detail Panel displays its updated content:

WishDetailPanelSequenceDiagram
Design Considerations

Aspect: How to update the progress and saving history on UI

  • Alternative 1 (current choice): Clear all the sub components and add new sub components accordingly

    • Pros: No matter which progress or history is changed, or what type of change (ie. delete, add, or edit), this change can be handled by the same method each time.

    • Cons: It is redundant to clear everything and replace them with new sub components.

  • Alternative 2: Handle different kinds of changes to the progress or history lists.

    • Pros: It is a lot faster to only change the sub component that is affected.

    • Cons: There are too many cases for how the lists can be changed. (ie. a different change is needed for each of these cases: wish is deleted/edited/created/cleared, or a wishTransaction is deleted/added)

3.9. [Proposed] Savings Notifications

3.9.1. Justification

Some users may have many wishes, all of which have a different targeted date of completion and different price. It may thus be difficult for users to keep track of how much they need to consistently save to fulfil their various wishes on time. This Savings Notification feature will allow users to opt for daily/weekly/monthly notifications for each specific wish, reminding them of the amount that they need to save at the beginning of the chosen time period. This will help users to consistently save towards their wishes.

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 using LogsCenter.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 App name, 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.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 10. Saving documentation as PDF files in Chrome

4.4. Site-wide Documentation Settings

The 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.
Table 1. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

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.
Table 2. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

4.6. Site Template

The files in 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 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 docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

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 choose Run '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:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.url.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.url.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.url.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.url.logic.LogicManagerTest

5.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, HelpWindow.html in src/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.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. 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, Wish Book depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

Target user profile:

  • has a need to manage savings for a significant number of items to buy

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

Value proposition: manage savings faster than a typical mouse/GUI driven app

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…​

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* * *

user

add a new wish

keep track of the things I want to purchase

* * *

user

add savings to selected wishes

make faster progress towards certain wishes

* * *

user

delete a wish

remove items that I no longer need

* * *

user

find a wish by name

locate details of a wish without having to go through the entire list

* * *

user

view all fulfilled wishes

so I can keep track of items I have bought

* * *

user

view all past savings for my wishes

have a better idea of my saving habits in general

* * *

user

view all my wishes

monitor the progress I have made in all my wishes

* * *

user

undo past commands

reverse wrong commands

* *

user

distribute a saving to a few wishes

make equal progress to a few of my wishes

* *

user

rank my wishes

prioritise certain wishes over others so that money can be allocated accordingly

* *

user

transfer money from one wish to another

progress towards other wishes faster

* *

user

withdraw from savings

spend the money if need be

* *

user

reorder the priority of a wish

fulfil the specified wish faster

* *

user

save money without a wish

allocate my savings to a wish later

*

user

receive email reminders about wishes that are due

be more mindful of my savings to fulfil wishes

*

user

view all past savings for a particular wish

have a better idea of my saving habits for a wish

Appendix C: Use Cases

(For all use cases below, the System is the WishBook and the Actor is the user, unless specified otherwise)

Use case: Add wish

MSS

  1. Actor enters a wish with Name, Date, Price.

  2. System adds wish to the wish list.

    Use case ends.

Extensions

  • 2a. Actor fails to specify any of the compulsory fields (Name, Price, and Date/Duration).

    • 2a1. System shows a correct Add command usage with example.

      Use case ends.

  • 2b. Actor enters incorrectly formatted arguments.

    • 2b1. System shows Add command usage.

    • 2b2. Actor is prompted to enter a valid argument in a specific format shown.

      Use case ends.

Use case: Delete wish

MSS

  1. Actor requests to list wishes.

  2. System shows a list of wishes.

  3. Actor requests to delete a specific wish in the list.

  4. System deletes the wish.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. System shows an error message.

      Use case resumes at step 2.

    • 3b1. Wish requested to be deleted has a non-zero savings amount.

    • 3b2. System displays warning to user that wish to be deleted has a non-zero savings amount.

      Use case resumes at step 2.

Use case: Edit wish

MSS

  1. Actor requests to edit wish.

  2. System updates wish and shows updated wish to Actor.

    Use case ends.

Extensions

  • 1a. System has no recorded wishes.

    • 1a1. Actor is prompted to add a wish.

      Use case ends.

  • 1b. Actor enters invalid arguments

    • 1b1. System shows Edit command usage.

    • 1b2. Actor is prompted to enter a valid argument.

      Use case ends.

Use case: Find wishes

MSS

  1. Actor specifies the search predicate.

  2. System shows all wishes matching the given search predicate.

    Use case ends.

Extensions

  • 1a. System has no recorded wishes.

    • 1a1. System shows dialog notifying Actor that no relevant results can be found.

      Use case ends.

  • 1b. Actor enters invalid arguments

    • 1b1. System shows Find command usage.

    • 1b2. Actor is prompted to enter a valid argument.

      Use case ends.

  • 1c. System unable to find any matching wishes.

    • 1c1. System shows dialog notifying Actor that no relevant results can be found.

      Use case ends.

  • 1d. Actor enters empty prefixes for arguments.

    • 1c1. System shows all wishes in the WishBook.

      Use case ends.

Use case: Move amount between wishes

MSS

  1. Actor specifies the origin wish index, the destination wish index, and amount to move.

  2. System moves specified amount from origin wish to destination wish.

    Use case ends.

Extensions

  • 1a. System has no recorded wishes.

    • 1a1. Actor is prompted to add a wish.

      Use case ends.

  • 1b. Actor enters invalid arguments.

    • 1b1. System shows Move command usage.

    • 1b2. Actor is prompted to enter a valid argument.

      Use case ends.

  • 1c. Actor enters the same index for origin wish and destination wish.

    • 1c1. System shows Move command usage.

    • 1c2. Actor is prompted to enter a valid argument.

      Use case ends.

  • 1d. The amount to move is greater than the existing saved amount in the origin wish.

    • 1d1. System reports that saved amount of wish cannot become negative.

      Use case ends.

  • 1e. The amount moved to destination wish exceeds the amount needed to fulfil the wish.

    • 1e1. System moves to destination wish the required amount needed to fulfil it.

    • 1e2. System moves the excess amount to unused funds.

      Use case ends.

  • 1f. Actor specifies unused funds as the origin wish.

    • 1f1. System moves amount from unused funds to the destination wish.

      Use case ends.

  • 1g. Actor specifies unused funds as the destination wish.

    • 1g1. System moves amount from origin wish to unused funds.

      Use case ends.

Use case: Save money for a wish

MSS

  1. Actor enters X amount of money to be saved.

  2. System transfers X to the wish with the specified index.

    Use case ends.

Extensions

  • 1a. Actor specifies to allocate the money to unused funds.

    • 1a1. System adds X to unused funds.

      Use case ends.

  • 1b. System has no recorded wishes.

    • 1b1. Actor is prompted to add a wish.

      Use case ends.

  • 1c. Actor enters an invalid value of money to be saved.

    • 1c1. Actor is prompted to enter a valid value.

      Use case ends.

  • 1d. Actor enters an amount of money that causes the wish’s saved amount to exceed the amount needed to fulfil the wish.

    • 1d1. System adds the required amount to fulfil the saved amount of the wish at the specified index.

    • 1d2. System adds the excess amount to unused funds.

      Use case ends.

  • 1e. Actor enters an amount that causes the wish’s resulting saved amount to become negative.

    • 1e1. Actor is prompted to enter a valid value.

      Use case ends.

Use case: View all wishes

MSS

  1. Actor requests to view all wishes.

  2. System shows all wishes.

    Use case ends.

Extensions

  • 1a. System has no recorded wishes.

    • 1a1. User is prompted to add a wish.

      Use case ends.

  • 1b. System has no recorded wishes.

    • 1b1. System shows dialog notifying Actor that there are no such wishes.

      Use case ends.

Use case: View uncompleted wishes

MSS

  1. Actor requests to view uncompleted wishes.

  2. System shows all uncompleted wishes.

    Use case ends.

Extensions

  • 1a. System has no recorded uncompleted wishes.

    • 1a1. User is prompted to add a wish.

      Use case ends.

  • 1b. System has no recorded uncompleted wishes.

    • 1b1. System shows dialog notifying Actor that there are no such wishes.

      Use case ends.

Use case: View completed wish list

MSS

  1. Actor requests to list completed wishes.

  2. System shows a list of completed wishes.

    Use case ends.

Extensions

  • 2a. List is empty.

    Use case ends.

Use case: View command history

MSS

  1. Actor requests to view history of commands entered.

  2. System shows all commands entered.

    Use case ends.

Extensions

  • 1a. System has no recorded commands.

    • 1a1. User is prompted to enter a command.

      Use case ends.

Use case: View savings history

MSS

  1. Actor requests to view history of savings entered.

  2. System shows all savings entered, from newest to oldest.

    Use case ends.

Extensions

  • 1a. System has no recorded savings.

    • 1a1. User is prompted to enter a saving.

      Use case ends.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 9 or higher installed.

  2. Should be able to hold up to 1000 wishes without user experiencing a drop in application performance.

  3. 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.

  4. User data can be transferred across different machines (of different platforms).

  5. The software should not use a DBMS (Database Management System) to store data.

  6. User data is stored locally.

  7. User data is human readable and can be edited.

  8. Friendly towards color-blind users.

  9. Command Line Interface (CLI) is the primary mode of input. GUI is used mainly for visual feedback rather than to collect input. Usage of mouse should be minimized.

  10. The software should follow the Object-Oriented Paradigm.

  11. The software should work without requiring an installer.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X.

Index

Order of priority of a wish.

Wish

Something the user wants to save up money for.

Wishlist

A record of all wishes added by the user.

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

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

F.2. Deleting a wish

  1. Deleting a wish while all wishes are listed

    1. Prerequisites: List all wishes using the list command. Multiple wishes in the list. Wish at index 1 should be fulfilled.

    2. Test case: delete 1
      Expected: First wish is deleted from the list. Details of the deleted wish shown in the status message. Unused funds will not be updated as the product has already been bought. Deleted wish details and amount in unused funds shown in status message. Timestamp in the status bar is updated.

    3. Test case: delete 2
      Expected: Second wish is deleted. Money in the wish will be transferred to unused funds. Deleted wish details and amount in unused funds shown in status message. Timestamp in the status bar is updated.

    4. Test Case: delete
      Expected: Error. Invalid command format message shown in unused funds.

    5. Test Case: delete -1
      Expected: Error. Invalid command format message shown in unused funds.

    6. Other incorrect delete commands to try: delete x (where x is larger than the list size)
      Expected: Error. Invalid wish provided message in message status.

F.3. Saving to a wish

  1. Saving to a wish

    1. Prerequisites: Add a new wish with the add command with a date earlier than the earliest wish in the WishBook. If the current date is past the earliest wish’s date, delete the first wish until this is no longer true and then add a new wish earlier than the earliest wish in the WishBook.

    2. Test case: save 1 0
      Expected: 0.00 saved to first wish. Details of saved amount and the remaining amount left for completion shown in the status message. Timestamp in the status bar is updated.

    3. Test case: save 1 0.01
      Expected: 0.01 saved to first wish. Details of saved amount and the remaining amount left for completion shown in the status message. Timestamp in the status bar is updated.

    4. Test case: save 1 -0.01
      Expected: 0.01 removed from first wish. Details of removed amount and the remaining amount left for completion shown in the status message. Timestamp in the status bar is updated.

    5. Test case: save 1 [VALUE LARGER THAN WISH PRICE]
      Expected: Wish will be 100% and fulfilled. Details of the excess amount saved and the unused funds value shown in status message.

    6. Test case: save 1 5
      Expected: Unable to save to wish. Wish already fulfilled message shown in status message. Timestamp in status bar is updated.

    7. Other incorrect delete commands to try: save 2 0.001, save 2 12., save 2 12@
      Expected: Similar to previous.

F.4. Moving funds between wishes

  1. Moving funds between 2 wishes:

    1. Prerequisites: Wish at index 1 should be fulfilled. If it is not, save until it is. The other wishes used in the subsequent commands should not be fulfilled.

    2. Test case: move 1 2 50
      Expected: No money moved from wish 1 to 2. Wish already fulfilled status message shown.

    3. Test case: move 2 3 [AMOUNT SMALLER THAN SAVED AMOUNT AT FROM_INDEX]
      Expected: Specified amount moved from wish 2 to wish 3. Moved message shown in status message. Timestamp in status bar is updated.

    4. Test case: move 2 3 [AMOUNT LARGER THAN SAVED AMOUNT AT FROM_INDEX]
      Expected: No money moved from wish 1 to 2. FROM_INDEX does not contain the specified amount shown in status message.

    5. Test case: move 2 3 [AMOUNT LARGER THAN SAVED AMOUNT AT TO_INDEX]
      Expected: Money moved from wish 2 to 3. Wish 3 is fulfilled. Excess money goes to unused funds(index 0). Moved amount, excess and updated unused funds shown in status message. Timestamp in status bar is updated. ed. Incorrect move commands to try: move 2 3 -50, move 2 3 0.0001 Expected: No money will be moved. Appropriate error message shown in status message.

F.5. Adding a wish

  1. Adding to a wish

  2. Positive test cases

    1. Prerequisites: None. Duplicate Wishes can exist and have the same user-defined properties.

    2. Test case: add n/PS4 Golden Pro d/25/12/2018 p/499.99
      Expected: A new Wish named “PS4 Golden Pro”, priced at $499.99, and expiry date set to 25th December 2018, default URL of “www.amazon.com” will be added to the list of Wishes. Status message shows the details of the newly added Wish.

    3. Test case: add n/PS4 Golden Pro d/25/12/2018 p/499.99 t/technology
      Expected: A new Wish named “PS4 Golden Pro”, priced at $499.99, and expiry date set to 25th December 2018, default URL of “www.amazon.com”, with a tag named “technology”, will be added to the list of Wishes. Status message shows the details of the newly added Wish. Selecting on the newly added Wish will also bring up the Amazon’s homepage on the right side of the Detail Panel.

    4. Test case: add n/PS4 Golden Pro a/60d p/499.99
      Expected: A new Wish named “PS4 Golden Pro”, priced at $499.99, and expiry date set to your current date plus 60 days, default URL of “www.amazon.com” will be added to the list of Wishes. Status message shows the details of the newly added Wish. Selecting on the newly added Wish will also bring up the Amazon’s homepage on the right side of the Detail Panel.

    5. Test case: add n/PS4 Golden Pro a/2y p/499.99 u/www.google.com
      Expected: A new Wish named “PS4 Golden Pro”, priced at $499.99, and expiry date set to your current date plus 2 years, default URL of “www.amazon.com” will be added to the list of Wishes. Status message shows the details of the newly added Wish. Selecting on the newly added Wish will also bring up the Google search engine webpage on the right side of the Detail Panel.

  3. Negative test cases

    1. Prerequisites: None.

    2. Test case: add n/PS’4 Golden Pro a/30d p/499.99
      Expected: Status message shows that a Wish’s name can only contain alphanumeric numbers and spaces, and should not be blank.

    3. Test case: add n/ a/30d p/499.99
      Expected: Status message shows that a Wish’s name can only contain alphanumeric numbers and spaces, and should not be blank.

    4. Test case: add n/PS4 Golden Pro a/30d3y p/499.99
      Expected: Status message shows the given time format is invalid, and also shows the user’s time input.

    5. Test case: add n/PS4 Golden Pro d/30.30.9999 p/499.99
      Expected: Status message shows that the time should be of format: dd/mm/yy.

    6. Test case: add n/PS4 Golden Pro a/3d p/499.9999999
      Expected: Status message states that price value should only contain numbers, and at most two numbers after the decimal point.

    7. Test case: add n/PS4 Golden Pro a/3d p/abc.de
      Expected: Status message states that price value should only contain numbers, and at most two numbers after the decimal point.

    8. Test case: add n/PS4 Golden Pro a/-1d p/22.22
      Expected: Status message states that the given expiry date should be after the current date.

    9. Test case: add n/PS4 Golden Pro d/25/12/1111 p/22.22
      Expected: Status message states that the given expiry date should be after the current date.

    10. Test case: add n/PS4 Golden Pro a/1d
      Expected: Status message states that this is an invalid command format, and shows the correct usage of add command.

    11. Test case: add n/PS4 Golden Pro d/25/12/2018 p/22.22 u/abc def.com
      Expected: Status message states that URLs cannot have whitespaces.

F.6. Listing out wishes

  1. Listing out wishes in the list

  2. Prerequisites: Have both completed (amount saved more than price) and uncompleted wishes.

    1. Test case: list
      Expected: All wishes in WishBook is listed.

    2. Test case: list -c
      Expected: Only wishes that are completed will be shown on the list.

    3. Test case: list -u
      Expected: Only wishes that are uncompleted will be shown on the list.

F.7. Finding wishes

  1. Finding a wish by name, tags or remark:

  2. Prerequisites: None

    1. Test case: find r/Alice
      Expected: Returns all the wishes whose remark contains “Alice” case insensitive. Number of wishes found is listed in status message. Only matched wishes visible in WishList.

    2. Test case: find t/neigh t/paren
      Expected: Returns all the wishes whose tags contain the keywords “neigh” or “paren”. Number of wishes found is listed in status message. Only matched wishes visible in WishList.

    3. Test case: find n/EVG t/PS
      Expected: Returns all wishes whose name contains “EVG” and whose tag contains “PS” case insensitve. Number of wishes found is listed in status message. Only matched wishes visible in WishList.

    4. Test case: find n/EVG n/PS t/neigh t/paren
      Expected: Returns all wishes whose name contains (“EVG” or “PS”) and (“neigh” or “paren”). Number of wishes found is listed in status message. Only matched wishes visible in WishList.

    5. Test case: find
      Expected: Invalid command. Invalid command format message shown in status message.

    6. Other incorrect delete commands to try: find test, find d/22/10/1996.
      Expected: Similar to previous.

F.8. Saving data

  1. Dealing with missing/corrupted data files

    1. Prerequisites: content of WishBook.xml and wishTransaction.xml files will be used to load data for a new session of WishBook. If data is missing from any of these files, data will be lost from subsequent sessions of WishBook unless the xml file data is restored and in proper format.

    2. Test case (gracefully exiting app assuming that WishBook contains more than 1 wish): save 1 10 and then exit
      Expected: when a new session is started, WishBook should contain all wishes from the previous session. Wish in which the save command is performed for should have its saving history and save amount updated.

    3. Test case (WishBook data and wishTransaction data should be backed up everytime the state of WishBook changes): save 1 10
      Expected: assuming WishBook contains more than 1 wish, the execution of the save command should cause the WishBook.xml and wishTransaction.xml files to be updated with a new save amount.

    4. Test case (empty WishBook to simulate missing data) Copy the contents of the empty xml file after executing the clear command to simulate missing file data into WishBook.xml at the file path specified in the save location (which is specified in the preferences.json file)
      Expected: When the WishBook is loaded, all wish data should be absent.

    5. Test case (corrupted data): delete data for 1 wish currently in WishBook by editing the WishBook.xml file.
      Expected: assuming that the data format of the xml file is valid, WishBook should load the content of the WishBook based on the data present in WishBook.xml file (without the deleted wish data). If the data format of the xml file is invalid the WishBook will not load properly and an error message should appear.