By: Team CS2103T-T13-4      Since: Sept 2019      Licence: MIT

1. Introduction

Welcome to Mark. This developer guide aims to introduce potential developers to the structure and implementation of Mark, so that you can contribute too!

The guide first covers the high-level design of Mark, and then discusses the implementation of key features and the rationale behind certain design decisions. Next, it provides links to guides for the tools used in Documentation, Testing, and DevOps. Finally, the appendices of this guide specify the product scope, requirements, a glossary, and instructions for manual testing.

2. Setting up

Refer to the guide here.

3. Design

This section shows the design of Mark.

3.1. Architecture

ArchitectureDiagram
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 .puml files used to create diagrams in this document can be found in the diagrams folder.

Main has two classes called Main and MainApp. It is responsible for,

  • Initializes the components in the correct sequence, and connects them up with each other.

  • Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. For example, LogsCenter plays an important role at the architecture level as it is used by many classes to write log messages to the App’s log file.

The rest of the App consists of the following four components:

  1. UI: The UI of the App.

  2. Logic: The command executor.

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

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

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.

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete 1 command

The sections below give more details of each component.

3.2. UI component

The following diagram illustrates the structure of the UI component:

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, BookmarkListPanel, StatusBarFooter etc. All these, including the MainWindow and 'TabView' components (not shown in the above figure), inherit from the abstract UiPart class.

The structure of the TabView is depicted here:

UiTabViewClassDiagram
Figure 5. Structure of the TabView Component, which is a part of the UI component

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 does the following:

  • Executes user commands using the Logic component.

  • Listens for changes to Model data so that the UI can be updated with the modified data.

3.3. Logic component

The following class diagram illustrates the structure of the Logic component:

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

Logic uses the MarkParser class to parse the user command. This results in a Command object which is executed by the LogicManager. The command execution can affect the Model (e.g. adding a bookmark) and/or Storage (e.g. import bookmarks). The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user, switching the tab view, or exiting the application.

As an example of how the Logic component works, the following sequence diagram shows the interactions within the Logic component for the execute("delete 1") API call:

DeleteSequenceDiagram
Figure 7. Interactions Inside the Logic Component for the delete 1 Command
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

The Logic component does the following:

  • Parses and executes user commands

  • Modifies Model and/or Storage according to user commands

  • Instructs Ui through CommandResult objects

3.4. Model component

The following class diagram illustrates the structure of the Model component:

Model Class Diagram
Figure 8. Structure of the Model Component

API : Model.java

The Model component manages the storage of data in Mark. Hence, it does not depend on any of the other three components.

As seen in the diagram above, the Model consists of the following:

  1. VersionedMark - contains Mark data

  2. UserPrefs - represents the user’s preferences

The Model also exposes several Observable properties so that the UI can 'observe' them and update automatically when Model data are changed. For simplicity’s sake, some of these dependencies have been omitted from the Model class diagram. The observable properties are:

  1. An unmodifiable filtered ObservableList of bookmarks - represents the bookmarks that are currently being displayed in the bookmark list.

  2. A second filtered ObservableList of bookmarks - represents the bookmarks that are currently being displayed in the favorites panel on the Dashboard.

  3. A SimpleObjectProperty of a Url - contains the URL of the current web page that can be seen on the Online tab.

  4. A SimpleObjectProperty of a Bookmark - represents the Bookmark whose Offline Copy is currently being viewed in the Offline tab.

As seen in Figure 8, “Structure of the Model Component”, VersionedMark contains a list of ReadOnlyMark objects that represent past or future states of Mark. The class Mark implements ReadOnlyMark. Each of the Mark objects in VersionedMark comprises a UniqueBookmarkList from the Bookmark package, a ReminderAssociation object from the Reminder package, an AutotagController from the Autotag package, and a FolderStructure from the FolderStructure package.

The structure of each package will now be explained in more detail.

Bookmark Package Class Diagram
Figure 9. Structure of the Bookmark Package

The Bookmark package contains classes relating to Bookmarks and their attributes, as shown above. The external class Mark contains a UniqueBookmarkList, which has one or more Bookmarks. As mentioned earlier, the Bookmark class is also accessed by ModelManager.

Each Bookmark has a Name, a Url, a Remark, a Folder, zero or one CachedCopy, and one or more Tags. Each CachedCopy has its own set of annotations (in the Annotation package).

Each Bookmark has a different Tag object and a different Folder object, even if bookmarks have the same tag or folder name. More details regarding the implementation of Folders can be found in Section 4.4, “Folders feature”.
Annotation Package Class Diagram
Figure 10. Structure of the Annotation Package

The above diagram shows the structure of the Annotation package, which is accessed by ModelManager via OfflineDocument. An OfflineDocument consists of zero or more Paragraph objects, which are each identified by a ParagraphIdentifier. A Paragraph can either be a TrueParagraph, which has a ParagraphContent and zero or one Annotation, or a PhantomParagraph, which has no ParagraphContent but must have an Annotation with an AnnotationNote. In general, an Annotation consists of a Highlight and an AnnotationNote.

More details regarding the implementation of Annotations can be found in Section 4.3, “Annotation feature”.
Autotag Package Class Diagram
Figure 11. Structure of the Autotag and Predicates Packages

The Autotag package consists of a main AutotagController class, which controls one or more SelectiveBookmarkTaggers. The SelectiveBookmarkTagger class contains a BookmarkPredicate and a Tag (inherited from its parent class BookmarkTagger). Each BookmarkPredicate, in turn, makes use of a combination of predicates like NameContainsKeywordsPredicate, UrlContainsKeywordsPredicate, and FolderContainsKeywordsPredicate to test bookmarks.

More details regarding the implementation of Autotags can be found in Section 4.2, “Autotag feature”.
Reminder Package Class Diagram
Figure 12. Structure of the Reminder Package

The Reminder package is managed by the ReminderAssociation class. It handles the association between a set of Reminders and a set of Bookmarks, making sure that the two sets remain synchronised. Each Reminder consists of a Note that describes the task to be done and the Url of the Bookmark it is associated with.

More details regarding the implementation of Reminders can be found in Section 4.5, “Reminder feature”.

3.5. Storage component

The following diagram illustrates the structure of the Storage component:

StorageClassDiagram
Figure 13. Structure of the Storage Component

API : Storage.java

The Storage component does the following:

  • Saves UserPref objects in json format and reads them back.

  • Saves the Mark data in json format and reads it back.

3.6. Common classes

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

4. Implementation

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

4.1. Undo/Redo feature

The Undo and Redo feature is crucial to give users the flexibility of reverting wrongly executed commands. This section explains the implementation of the undo/redo mechanism as well as some design considerations when implementing this feature.

4.1.1. Implementation

The undo/redo mechanism is facilitated by VersionedMark. It extends Mark with an undo/redo history, stored internally as a list of markStateRecord and currentPointer. The class diagram for MarkStateRecord and its relationship with ReadonlyMark, Mark and VersionedMark is shown below:

MarkStateRecordClassDiagram

As shown in the class diagram, VersionedMark implements the following operations:

  • VersionedMark#save(String record) — Saves the current Mark state and the corresponding record to its history.

  • VersionedMark#undo(int step) — Undoes the given number of actions and returns the record which consists of the list of actions being undone.

  • VersionedMark#redo(int step) — Redoes the given number actions and returns the record which consists of the list of actions being undone.

These operations are exposed in the Model interface as Model#saveMark(String record), Model#undoMark(int step) and Model#redoMark(int step) 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 VersionedMark will be initialized with the initial Mark state and an empty record, and the currentPointer pointing to that single state record.

UndoRedoState0

Step 2. The user executes delete 5 command to delete the 5th bookmark in the Mark. The delete command calls Model#saveMark(String record), causing the modified state of the Mark and the record of the command after command execution to be saved in the markStateRecords, and the currentPointer is shifted to the newly inserted state record.

UndoRedoState1

Step 3. The user executes add u/www.google.com …​ to add a new bookmark. The add command also calls Model#saveMark(String record), causing another modified Mark state and the record to be saved into the markStateRecords, and the currentPointer is again shifted to the newly inserted state record..

UndoRedoState2
If a command fails its execution, it will not call Model#saveMark(String record), so the Mark state will not be saved into the markStateRecords.

Step 4. The user now decides that adding the bookmark was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoMark(1) since by default the number of steps to undo is 1 if not specified. This command shifts the currentPointer once to the left, pointing it to the previous state record, and restores the Mark to that state.

UndoRedoState3
If the currentPointer is at index 0, pointing to the initial Mark state, then there are no previous Mark states to restore. The undo command uses Model#canUndoMark(1) to check the if there are enough commands to undo. In this case, 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:

UndoSequenceDiagram
The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

The redo command does the opposite — it calls Model#redoMark(int step), which shifts the currentPointer to the right by the given number of steps, and restores the Mark to the state corresponding to the currentPointer.

If the currentPointer is at index markStateRecords.size() - 1, pointing to the latest Mark state, then there are no undone Mark states to restore. The redo command uses Model#canRedoMark(1) to check the if there are enough commands to redo. In this case, 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 Mark, such as find, will usually not call Model#saveMark(String record), Model#undoMark(int step) or Model#redoMark(int step). Thus, the markStateRecords remains unchanged.

UndoRedoState4

Step 6. The user executes clear, which calls Model#saveMark(String record). Since the currentPointer is not pointing at the end of the markStateRecords, all state records after the currentPointer will be purged. We designed it this way because it no longer makes sense to redo the add u/www.google.com …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoState5

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

SaveMarkActivityDiagram

4.1.2. Design Considerations

This section explains some of the design considerations we had when implementing this feature.

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

    • 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 bookmark being deleted).

    • Cons: 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 Mark state records.

    • Pros: Undo/redo multiple commands can be achieved in O(1) time by just shifting the current pointer.

    • Cons: Clear state records after the current pointer can take O(n) time.

  • Alternative 2: Use two stacks, an undo stack and a redo stack to store the history of Mark state records.

    • Pros: Clear state records after the current pointer can be achieved in O(1) time by clearing the redo stack.

    • Cons: Undo/redo multiple commands can take O(n) time as each undo/redo action requires popping a state record from the undo stack and pushing it into the redo stack. There is no way to pop multiple state records efficiently at one time.

Aspect: Commands to include for undo/redo
  • Alternative 1 (current choice): Include only commands that modify the state of Mark data.

    • Pros: Only meaningful app data is stored, less memory is consumed

    • Cons: Unable to retrieve commands that do not alter the state of Mark, such as find, list

  • Alternative 2: Include all commands

    • Pros: All commands can be undone and redone

    • Cons: More data to be stored and these data might not be meaningful to the users. More data related to Command class also needs to be stored and VersionedMark might need to depend on Logic which breaks the layered architecture of the existing application

4.2. Autotag feature

4.2.1. Implementation

Autotags, autotag names, and autotag conditions are represented as SelectiveBookmarkTaggers, Tags and BookmarkPredicates in the Model respectively. The class diagram for the Autotag package illustrates the structure of these classes:

300
Figure 14. Class diagram for the Autotag package

The BookmarkPredicate class keeps track of multiple types of autotag conditions. To do so, it maintains separate sets of keywords for each condition category; for instance, name keywords are stored separately from not-URL keywords. It also contains a single predicate to test whether a bookmark matches the given conditions. Other classes in the Predicates package (those with names XYZContainsKeywordsPredicate) are used to generate this predicate from the keyword sets.

You can refer to the Autotags section of the User Guide for details of autotag conditions.

The autotag mechanism itself is facilitated by the main class AutotagController, which stores and manages the list of SelectiveBookmarkTaggers. The AutotagController implements several operations to add, remove, and apply taggers, as well as to check whether a given tagger exists. Four of these operations can be accessed via the Model interface: hasTagger(SelectiveBookmarkTagger), addTagger(SelectiveBookmarkTagger), removeTagger(String), and applyAllTaggers(). These Model operations allow autotags to be added, edited, or removed by commands in the Logic component.


Given below is an example usage scenario that shows the autotag mechanism at each step.

Step 1. The user opens the application with an existing list of bookmarks and no autotags.

400
Figure 15. The initial state of Mark

Step 2. The user executes the command autotag Help u/stackoverflow.com/questions to add an autotag that tags all bookmarks from stackoverflow.com/questions with the tag Help.

  • A SelectiveBookmarkTagger is created with a BookmarkPredicate and a Tag named Help. The predicate’s URL keyword is stackoverflow.com/questions. Bookmark b2, which matches the predicate’s conditions, is replaced by a copy of itself (b3) that contains the additional tag Help.

450
Figure 16. The state of Mark after adding an autotag

Step 3. The user then executes the command add n/JavaFX new scene u/https://stackoverflow.com/questions/29080759/ to bookmark a question on StackOverflow.

  • A new bookmark with the name JavaFX new scene is created. This bookmark matches the conditions for the autotag Help, so it is tagged Help.

450
Figure 17. The state of Mark after adding and automatically tagging a new bookmark

The next three diagrams show how the autotag command works in more detail.

An autotag command is parsed using an AutotagCommandParser in a similar fashion as other commands, resulting in an AutotagCommand that contains the SelectiveBookmarkTagger to be added. The following diagram illustrates the sequence of operations that occur when a valid autotag command is executed:

300
Figure 18. Sequence diagram showing the general execution of an AutotagCommand

The AutotagCommand first checks whether the given model contains the tagger to be added. Since it does not (the autotag command is valid), the tagger is added and all taggers are applied to the bookmarks in model. The current state of Mark is then saved.

The next sequence diagram provides details of how taggers are applied to bookmarks in Mark.

500
Figure 19. Sequence diagram showing the execution of Model#applyAllTaggers()
The sd frame should cover the whole diagram, but it does not due to a limitation of PlantUML.

As seen above, the Model calls VersionedMark, which obtains its list of bookmarks and passes the list to AutotagController. AutotagController then iterates through the bookmarks and taggers, applying tags to bookmarks by using SelectiveBookmarkTagger#applyTagSelectively(Bookmark). A new list of bookmarks is returned, which is set as the new bookmark list.

The activity diagram below summarizes what happens when a valid autotag is added. The mechanism for tagging bookmarks when a bookmark is added or modified is similar.

300
Figure 20. Activity diagram showing how an autotag is added

4.2.2. Design Considerations

This section explains the key reasons why certain implementations were selected over others when designing the autotag mechanism.

Aspect: How to tag bookmarks based on specific conditions
  • Alternative 1: Implement all the logic in a single class, which first checks whether a bookmark matches its conditions, then tags the bookmark if it does.

    • Pros: Simpler to implement.

    • Cons: Violates the Single Responsiblity Principle.

  • Alternative 2 (current choice): Separate the checking and tagging mechanisms into two classes. One class implements the tagging, while the second class inherits that functionality and implements an additional check before tagging.

    • Pros: Allows the 'tagger' class to be re-used elsewhere.

    • Cons: Increases coupling between the two classes.

Aspect: How to apply taggers to a bookmark list in Mark
  • Alternative 1 (current choice): Replace the whole bookmark list with a new list of bookmarks, some of which have been tagged.

    • Pros: Simple to implement, maintains immutability of bookmarks.

    • Cons: Inefficient to construct a new list each time a single bookmark is tagged.

  • Alternative 2: Modify individual bookmarks when adding tags.

    • Pros: Eliminates the need to reset Mark’s bookmark list whenever taggers are applied.

    • Cons: Can cause unanticipated changes in other parts of the Model as bookmarks are modified.

  • Alternative 3: Replace only those bookmarks that were tagged.

    • Pros: Minimises performance issues from creating a new bookmark list.

    • Cons: More complicated to implement.

4.3. Annotation feature

4.3.1. Implementation

The annotation feature builds upon the structure of an offline document used to represent a Readability4J-derived cache. Such an offline document is represented by OfflineDocument, which contains at least one Paragraph. Each annotation is attached to an entire Paragraph. The annotation will be internally stored as an instance of Annotation. You can view the class structure of the annotation feature here.

The annotation feature supports adding, deleting and editing annotations. Command words annotate, annotate-delete and annotate-edit activate the respective functionality. These command words make up the family of annotation commands. When activated, each calls the respective command parser to create the corresponding AnnotationCommand. Subsequently, when necessary the appropriate Paragraph is retrieved, and the annotation is handled according to the command. You can study the hierarchy of the family of annotation commands here:

400
Figure 21. Structure of the family of annotation commands.

The following sequence diagram illustrates how a command of the family of annotation commands operates:

400
Figure 22. Sequence diagram showing how annotation commands generally work. cmdStr is the string representing the command input by the user. arguments is cmdStr without the command word.
The lifeline for AddAnnotationCommandParser and AddAnnotationCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

In the figure above, the specifics of how Parser#parse and AnnotationCommand#execute are abstracted out because each command is handled differently. The reference frame may refer to the following sequence diagrams for adding, deleting and editing annotations respectively:

350
Figure 23. Sequence diagram referenced by the general sequence diagram for annotations when cmdStr given is annotate 1 p/p2 n/example note h/orange. ARGS include pid, highlight and note.

The above example adds an annotation to a :TrueParagraph. Alternatively, if the user chooses to add a general note instead, OfflineDocument#addPhantom is called instead, creating an annotated :PhantomParagraph.

A :TrueParagraph, whose content is from the cache, must be highlighted in order to have a note attached. A :PhantomParagraph is a temporary holding place for a general note and has no content.
350
Figure 24. Sequence diagram referenced by the general sequence diagram for annotations when cmdStr given is annotate-delete 1 p/p2 n/true. ARGS include pid.

The above example deletes only the highlight from a :TrueParagraph which has a note, and then creates a :PhantomParagraph to keep a reference to the note.

For other annotate-delete functions, instances of other derived classes of DeleteAnnotationCommand is used instead of :DeleteAnnotationHighlightCommand.

If the user requests to remove the entire annotation instead, DeleteAnnotationAllCommand is used and the an:Annotation is simply dereferenced. If the user requests to remove only the annotation note, DeleteAnnotationNoteCommand is used and the AnnotationNote of Annotation is dereferenced. Alternatively, if the user requests to remove annotations from all paragraphs, DeleteAnnotationClearAllCommand is used and the entire :OfflineDocument is dereferenced and a new, unannotated copy is created.

350
Figure 25. Sequence diagram referenced by the general sequence diagram for annotations when cmdStr given is annotate-edit 1 p/g1 to/p1 h/green. ARGS include origPid, targetPid and newHighlight and newNote.

The above example moves general note G1 to paragraph P1 and highlights P1 green. As per the specifications, users are not allowed to move any annotation to :PhantomParagraph using annotate-edit, so targetP must either be null or a :TrueParagraph. However, origP can be anything as long as it is an instance of a derived class of Paragraph.

The reason why we do not allow users to use this command to make notes general is because there is not much value in doing so given we only support highlights and notes as annotations. If you want, you can easily extend the edit function to be able to allow moving annotations to the general section.

You will see why the sequence diagram for the annotate-edit command is much longer than that of annotate-delete in the Design Considerations section.

Summmary

The following activity diagram summarizes what happens when a user attempts to annotate their offline document:

350
Figure 26. Activity diagram for an annotation request.

4.3.2. Design Considerations

The following are a few design considerations made in deciding how to implement the annotation feature:

Aspect: How to structure the offline components to store annotations

An offline document is composed of multiple paragraphs, each having at most 1 annotation. A cached copy has the original cache of the website. There are hence a few alternatives in which we can combine these elements and store them:

  • Alternative 1 (current choice): Let each CachedCopy have an OfflineDocument that consists of paragraphs with content parsed from the original cache. Each paragraph contains a annotation, if any.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of memory usage and overheads from layers of abstraction.

  • Alternative 2: Let each cache keep only the original cache of the website. Use another class to store the annotations with the respective paragraph identifiers.

    • Pros: Will use less memory since the content of the website is not duplicated.

    • Cons: Not straightforward to implement. Stray notes will need a different implementation to order and store them. Objects not well abstracted.

I decided to proceed with Alternative 1 as it is easier to implement and more feasible to implement in light of time constraints. This alternative offers accessibility to model components that are highly related in functionality.

Aspect: How to implement command to delete annotations

An annotate-delete command deletes a part or whole of an annotation. In particular, deleting only the highlight of a paragraph with a note will have a side effect where the note becomes general. Considering these variations, there are a few alternatives that we can consider to implement the delete function:

  • Alternative 1: Use a single DeleteAnnotationCommand object to execute the entire delete functionality

    • Pros: Makes it clear as all related functionality are packaged in the same class.

    • Cons: Does not follow the Single Responsibility Principle and Open-close Principle. All checks will have to be performed in the same class, which makes it messy as well.

  • Alternative 2 (current choice): Use a different subtype of DeleteAnnotationCommand to represent and execute each use case.

    • Pros: Easier to implement since delete functionality is rather discrete and each function is mutually exclusive. Less checks will be required for each class implemented. Also easier to extend functionality in future.

    • Cons: Need to write multiple lines of code due to multiple layers of abstraction.

I decided to proceed with Alternative 2 as it is easy and feasible to implement in light of time constraints. Since the individual functions of annotate-delete do not overlap, this alternative makes it less cumbersome to modify if there needed to be a change in delete functionality.

Aspect: How to implement command to edit annotations

An annotate-edit command can edit existing annotations and/or moving annotations from paragraph to paragraph. As the functionality can be mixed, there are a few options we can take to implement the edit functionality:

  • Alternative 1 (current choice): Use a single EditAnnotationCommand object to execute the entire edit functionality

    • Pros: Easier to implement and also makes it clear as all related functionality are packaged in the same class.

    • Cons: Does not abide by the Single Responsibility Principle. All checks will have to be performed in the same class.

  • Alternative 2: Use a different subtype of EditAnnotationCommand to represent and execute each use case.

    • Pros: Easier to extend. Also, less checks on conditions can be performed for each class implemented.

    • Cons: Not straightforward to implement. The functions can overlap, which makes it difficult to segregate into separate responsibilities.

I decided to proceed with Alternative 1 as it is easier to implement. The overlaps in use cases of the annotate-edit command makes it difficult to separate the responsibility. Hence the first alternative is more feasible to implement.

4.4. Folders feature

This section aims to explore the implementation of Mark’s folders and some design considerations.

4.4.1. Implementation

The following two sections will explain in more detail how the backend and frontend of this feature works.

Model implementation of folders

A bookmark can be in a folder, and a folder can be nested within other folders for traditional directory organization.

This mechanism is facilitated mainly by Folder and FolderStructure.

Folder is simply another field in Bookmark, just like Url or Name, and has a single String property that contains the folder in which the bookmark is located.

FolderStructure represents the hierarchy of folders, containing the folder it represents and its subfolders

FolderStructureClassDiagram
Figure 27. Class Diagram of the Folders component

Given below is an example usage scenario and how the folder structure behaves at each step.

Step 1. The user launches the application for the first time. The root FolderStructure will be initialized with the initial hierarchy in the stored data.

FolderStructureState0

Step 2. The user enters a folder GER1000 p/Work command to create a new FolderStructure in the subfolders of work. Starting from the root, a depth first search will be performed to locate work. When found, ger1000 will be added to its subfolders.

FolderStructureState1
If the parent folder is not provided, the parent folder will default to root.

The following sequence diagram shows in more detail how the execution of folder GER1000 p/Work works:

FolderSequenceDiagram
The lifeline for AddFolderCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

Finally, the user sees his folder added successfully in the folder hierarchy.

UI implementation of the expand command

Mark displays the folders and their bookmarks in a JavaFX TreeView, and listens for any updates to the bookmarks or the folder structure via Observable s so that the UI can also be updated accordingly.

As Mark is mainly a CLI-based application, the expand command is provided to allow users to "click" on all unexpanded folders.

As the underlying folder structure is not altered, Oberservable s cannot help us and so the command must send a signal to the UI that an expand command needs to be performed.

Then, the following method is called to perform the expansion:

Code snippet from seedu.mark.ui.FolderStructureTreeView
 private void expand(TreeItem<String> node, int levels) {
    if (levels <= 0) {
        return;
    }
    if (node.isExpanded()) {
        node.getChildren().forEach(child -> expand(child, levels));
    } else {
        node.expandedProperty().set(true); // we expand it
        // make sure all children are not expanded
        node.getChildren().forEach(child -> child.setExpanded(false));
        expand(node, levels - 1);
    }
}

The method expands a folder if it is not already expanded, and then recursively calls itself on all of that folder’s subfolders until the desired level of expansion is achieved.

4.4.2. Design considerations

The following are a few design considerations made in deciding how to implement the folders feature.

Aspect: How the folder hierarchy is saved into storage

The following were two alternatives considered when implementing the model for the folders feature.

  • Alternative 1 (current choice): Saves bookmark folder as its own field, and the hierarchy as a separate data structure.

    • Pros: Easy to implement. Adding another field to a bookmark is simple, and storing the folder structure on its own makes it more modular and easy to test as well.

    • Cons: Easy for model to get into invalid state. For example, when renaming folders, the bookmarks containing the old folder names needs to be updated separately. This leads to tight coupling and potential future maintenance issues.

  • Alternative 2: Change the bookmarks from being stored in a list to being stored in a tree structure.

    • Pros: Single source of truth, a bookmark’s folder is simply which part of the tree it resides in. When a folder is renamed, the bookmarks in that folder will still be in the same folder, so there is no extra step needed to update the bookmarks. This reduces coupling between the bookmarks and folders too.

    • Cons: The whole BookmarkList abstraction will have to be rewritten. It is also significantly harder (in terms of ease of implementation) to filter, edit, and add bookmarks due to having to use more advanced tree traversal algorithms as compared to naive list or array operations.

Aspect: What the expand/collapse command does

In designing the expand and collapse command, the below two alternatives were considered. Only the expand command shall be elaborated upon, since the collapse command is simply the opposite of the expand command.

  • Alternative 1 (current choice): Expand to a certain level of folders.
    For example, when expand 2 is executed, the folder hierarchy will always display folders until two levels deep, regardless of its state before the command.

    • Pros: State is more easily managed. Mark can keep track of which level to expand until, and the UI can listen to changes to this via an Observable instead, leading to better separation of concerns.

    • Cons: Not intuitive for the user to understand. Since the folder hierarchy can be interacted with via the mouse also, the user may decide to expand the folders with his mouse and then execute expand 2, resulting in the folders expanding to two levels deep. This may result in the user thinking the folders were collapsed instead, if he had expanded his folders all the way before the command. This leads to confusion for the user.

  • Alternative 2: Go through all folders and check if they have been expanded. If a folder is not expanded, expand it. Repeat for the desired nmber of levels.

    • Pros: Easier for the user to understand. No matter what commands the user types or what he clicks, the expand command will always "expand" to a deeper level than what he originally saw (unless it was already expanded all the way).

    • Cons: An extra coupling is needed to tie the UI and the expand command together, since it cannot simply listen to changes from the model. This decreases extensibility and increases maintenance costs.

4.5. Reminder feature

4.5.1. Implementation

A bookmark can attach a reminder, and a reminder can be used to open the bookmark and send notifications to the user. This mechanism is facilitated by Reminder and ReminderAssociation.

Reminder contains a reminding time, the Url of the Bookmark and a Note.

ReminderAssociation represents the association between Reminder and Bookmark, containing the relation from Reminder to Bookmark and Reminder to Bookmark.

600
Figure 28. Class diagram of the Reminder component

There are four parts of the reminder feature:

  • Add a reminder to a specific bookmark (executed by AddReminderCommand#execute())

  • Edit a reminder (executed by EditReminderCommand#execute())

  • Delete a reminder (executed by DeleteReminderCommand#execute())

  • Goto the bookmark of a specific reminder (executed by GotoReminderCommand#execute())

These command are triggered by reminder command line entered into the command box, which will calls the respective command parser to create the command. The bookmark of the reminder will be retrieved and handled according to the command.

The following sequence diagram illustrates how the adding reminder operation works:

AddReminderSequenceDiagram
The lifeline for AddReminderCommandParser and AddReminderCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

In the reminder-delete command, instead of calling addReminder(bookmark, reminder) in Model, it will call removeReminder(reminder).

In the reminder-edit command, instead of calling addReminder(bookmark, reminder) in Model, EditReminderCommand will create a edited copy of the target reminder and call editReminder(target, editedReminder) in Model.

In the reminder-goto command, it will retrieved the bookmark of the reminder and generate a GotoCommandResult. It works the same as goto command of the bookmark.

The following activity diagram summarizes what happens when a user attempts to use a reminder command:

600

4.5.2. Design Considerations

Aspect: How to represent the relationship between reminder and bookmark
  • Alternative 1 (current choice): Use two hashmaps to store both the relationship from reminder to bookmark and bookmark to reminder.

    • Pros: More OOP and can easily find the relationship.

    • Cons: More complex when doing any operation to a reminder. Need to check two hashmaps.

  • Alternative 2: Reminder is a field of bookmark.

    • Pros: Easier to implement.

    • Cons: Reminder is a field of bookmark, so using a reminder to open the bookmark violate the relationship.

4.6. 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 4.7, “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

4.7. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

5. Documentation

Refer to the guide here.

6. Testing

Refer to the guide here.

7. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

The following characteristics describe our target user:

  • Needs to manage a significant number of bookmarks

  • Needs to make notes on webpage content quite frequently

  • Needs reminders to submit forms or visit websites

  • Prefers desktop apps over other types

  • Types quickly and prefers typing over mouse input

This is our value proposition – how Mark can streamline current web browsing-related activities:

  • Manage bookmarks faster than a typical mouse/ GUI driven app

  • Allow webpage content to be accessed and annotated without an Internet connection

  • Remind users about websites they need to visit at specific times

  • Eliminate the need for manual categorizing of bookmarks

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

learn how to use the app quickly

* * *

user

add a new bookmark

keep track of websites that I want to visit later

* * *

user

bookmark the current website being viewed

save the page without having to copy-and-paste the URL

* * *

user

delete a bookmark

remove bookmarks that I no longer need

* * *

user

find a bookmark by name, URL or tag

locate details of bookmarks without having to scroll through the entire list

* * *

user

edit a bookmark

make changes in case I type something wrongly or change my mind

* * *

user

undo the previous undoable command

reverse wrongly entered commands

* * *

user

redo the previous 'undo' command

reverse wrongly entered 'undo' command

* * *

user

add multiple tags to a bookmark

see what type of bookmark it is at a glance

* * *

user

automatically tag a bookmark based on certain conditions (such as its domain)

save time by avoiding the repeated tagging of bookmarks from the same domain

* * *

user with many bookmarks

organise all the bookmarks in a hierarchical folder structure

find my bookmarks more easily

* * *

user

move a bookmark to a different folder

re-organize my bookmarks when my needs change

* * *

user

open a bookmark within the App

easily visit a website without switching to other windows

* * *

user

view an offline copy of the content of a bookmark

still access the bookmarked page when there is no internet access

* * *

user

update the offline copy of bookmarks manually or automatically

keep offline copies updated

* * *

user

keep old offline copies

can refer to them in case information gets removed from the webpage

* * *

user

remove an specific offline copy

free up storage space by removing cached copies that I no longer need

* * *

user

highlight and annotate specific paragraphs in a bookmark

refer to the original content when reading my notes

* * *

user

edit highlighting and annotations

update my notes as I learn more about the content

* * *

user

remove highlighting and annotations

remove notes about content that is no longer important to me

* * *

user

add reminders for a bookmark

remember to submit online forms, to prepare for upcoming deadlines, etc.

* * *

user

edit a reminder

adjust my reminders when deadlines change

* * *

user

delete a reminder

remove reminders if I make a mistake when entering them

* * *

user

export and import all the bookmark data

easily migrate to another computer

* *

user

export and import bookmarks

share bookmarks with my friends

* *

user

favorite a bookmark

access it more easily in future

* *

user

mark a bookmark as 'read later'

know which bookmarks I have not read yet

* *

user

check my view history

see what websites I have visited

* *

user

automatically clean up old or outdated bookmarks

keep my bookmark list up to date without having to look through it regularly

*

user

export and import autotags

share my custom-made autotags with friends

Appendix C: Use Cases

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

Use case: UC01 - List bookmarks

MSS

  1. User requests to list bookmarks.

  2. Mark shows a list of bookmarks.

    Use case ends.

Use case: UC02 - Add a bookmark

MSS

  1. User requests to add a bookmark.

  2. User provides all the details of the bookmark to be added.

  3. Mark deletes the bookmark.

    Use case ends.

Extensions

  • 2a. Not all compulsory fields are provided.

    • 2a1. Mark shows an error message.

      Use case resumes at step 2.

  • 2b. The given URL is invalid.

    • 2b1. Mark shows an error message.

      Use case resumes at step 2.

Use case: UC03 - Delete a bookmark

MSS

  1. User lists bookmarks (UC01).

  2. User requests to delete a specific bookmark in the list.

  3. Mark deletes the bookmark.

    Use case ends.

Extensions

  • 1a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Mark shows an error message.

      Use case resumes at step 2.

Use case: UC04 - Edit bookmark

MSS

  1. User lists bookmarks (UC01).

  2. User requests to edit a specific bookmark in the list.

  3. Mark edits the bookmark.

    Use case ends.

Extensions

  • 2a. The given index is invalid.

    • 2a1. Mark shows an error message.

      Use case resumes at step 2.

  • 2b. No fields to edit are provided.

    • 2b1. Mark shows an error message.

      Use case resumes at step 2.

Use case: UC05 - Open bookmark

MSS

  1. User lists bookmarks (UC01).

  2. User requests to open a specific bookmark in the list.

  3. Mark opens the bookmark.

    Use case ends.

Extensions

  • 1a. The list is empty.

    Use case ends.

  • 2a. The given index is invalid.

    • 2a1. Mark shows an error message.

      Use case resumes at step 2.

Use case: UC06 - Export bookmarks

MSS

  1. User lists bookmarks (UC01).

  2. User requests to export all the bookmarks in this list.

  3. Mark creates a file containing the bookmarks on the hard disk.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. User does not specify a file name to be written to.

    • 3a1. Mark shows an error message.

      Use case resumes at step 4.

Use case: UC07 - Import bookmarks

MSS

  1. User requests to import bookmarks from a given file.

  2. Mark imports bookmarks from the specified file and displays the final list of bookmarks.

    Use case ends.

Extensions

  • 1a. No file with the specified file name is found.

    • 1a1. Mark shows an error message.

      Use case resumes at step 1.

  • 1b. The file format is invalid.

    • 1b1. Mark shows an error message.

      Use case ends.

Use case: UC08 - Make a bookmark available offline

MSS

  1. User requests to make a bookmark available offline.

  2. Mark downloads the bookmark and converts it with Readability.

    Use case ends.

Extensions

  • 2a. The bookmark’s URL is not available.

    Mark shows an error message.

    Use case resumes at step 1.

  • 3a. The given index is invalid.

    • 3a1. Mark shows an error message.

      Use case resumes at step 1.

Use case: UC09 - Undo previous undoable commands

MSS

  1. User requests to undo the previous undoable command.

  2. Marks restores the list to the state before the previous undoable command was executed.

    Use case ends.

Extensions

  • 1a. There is no command to undo.

    Mark shows an error message.

    Use case ends.

Use case: UC10 - Redo previously undone commands

MSS

  1. User requests to redo the previously undone command.

  2. Marks reverses the most recent ‘undo’ command.

    Use case ends.

Extensions

  • 1a. There is no previous undo command to redo.

    Mark shows an error message.

    Use case ends.

Use case: UC11 - Browse the web

MSS

  1. User requests to browse the web.

  2. Mark provides in-built web browser.

  3. User browses the web.

    Use case ends.

Extensions

  • 1a. Mark does not have internet access or url input is invalid.

    • 1a1. Mark tells user that to check that there is internet access and the url given is correct.

    Use case ends.

Use case: UC12 - Add bookmarks from web browser

MSS

  1. User browses the web (UC11).

  2. User requests to bookmark the currently showing webpage.

  3. Mark uses current website’s URL and adds bookmark (UC02).

    Use case ends.

Use case: UC13 - Annotate offline documents

MSS

  1. User chooses bookmark document to annotate.

  2. Mark shows the annotated document.

  3. User annotates the document.

  4. Mark shows document with new annotations.

    Use case ends.

Extensions

  • 1a. Mark detects invalid bookmark index or non-existent document of bookmark.

    • 1a1. Mark gives warning and requests for correct index.

    • 1a2. User enters revised data. Steps 1a1-1a2 are repeated until the data entered are correct.

      Use case resumes from step 2.

  • 3a. Mark detects invalid paragraph index.

    • 3a1. Mark gives warning and requests for correct paragraph index.

    • 3a2. User enters new data. Steps 3a1-3a2 are repeated until the data entered are correct.

      Use case resumes from step 4.

  • 3b. Mark detects invalid highlight colour.

    • 3b1. Mark gives warning and sets highlight colour to default colour.

      Use case resumes from step 4.

Use case: UC14 - Add a reminder

MSS

  1. User lists bookmarks (UC01).

  2. User requests to add a reminder for a specific bookmark in the list.

  3. Mark shows the new reminder in the reminder list.

    Use case ends.

Extensions

  • 1a. The list is empty.

    Use case ends.

  • 2a. Mark detects invalid index.

    • 2a1. Mark shows an error message.

      Use case resumes at step 1.

  • 2b. Mark detects the specified bookmark already has a reminder.

    • 2b1. Mark shows an error message.

      Use case resumes from step 1.

  • 2c. Mark detects the time format is wrong.

    • 2c1. Mark shows an error message.

      Use case resumes from step 1.

Use case: UC15 - Edit a reminder

MSS

  1. Mark shows a list of reminders.

  2. User requests to edit a specified reminder in the list.

  3. Mark shows the edited reminder in the reminder list.

    Use case ends.

Extensions

  • 1a. The list is empty.

    Use case ends.

  • 2a. Mark detects invalid index.

    • 2a1. Mark shows an error message.

      Use case resumes at step 1.

  • 2b. Mark detects the time format is wrong.

    • 2b1. Mark shows an error message.

      Use case resumes from step 1.

Use case: UC16 - Delete a reminder

MSS

  1. Mark shows a list of reminders.

  2. User requests to delete a specified reminder in the list.

  3. Mark shows the edited reminder list.

    Use case ends.

Extensions

  • 1a. The list is empty.

    Use case ends.

  • 2a. Mark detects invalid index.

    • 2a1. Mark shows an error message.

      Use case resumes at step 1.

Use case: UC17 - Open reminder

MSS

  1. User requests to open a specific reminder in the reminder list.

  2. Mark opens the reminder.

    Use case ends.

Extensions

  • 1a. The given index is invalid.

    • 1a1. Mark shows an error message.

      Use case resumes at step 1.

Appendix D: Non Functional Requirements

  1. The product should work on any mainstream OS as long as it has Java 11 or above installed.

  2. The product should be able to hold up to 500 bookmarks without a noticeable sluggishness in performance for typical usage.

  3. The product’s major features should not depend on Internet access.

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

  5. The project is expected to adhere to a schedule that delivers features in increments every two weeks.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Cache

The collection of offline copies of a specific bookmark

Undoable commands

Commands that modify the bookmark list, reminders, folders or annotations, which include add, edit, delete, clear, reminder, reminder-edit, reminder-delete, folder, folder-edit, folder-delete, autotag, autotag-delete, annotate, annotate-delete, annotate-edit, etc

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. Testing of general features

F.1.1. Changing tabs

  1. Changing from one tab to another

    1. Test case 1: tab 1
      Expected: View switches to Dashboard tab.

    2. Test case 2: tab on
      Expected: View switches to Online tab.

    3. Test case 3: tab off
      Expected: View switches to Offline tab.

F.1.2. Opening a bookmark

  1. Opening a bookmark while all bookmarks are listed

    1. Prerequisites: List all bookmarks using the list command. Multiple bookmarks in the list.

    2. Test case: goto 1
      Expected: Tab view is switched to the online tab if the current view is not online tab. First bookmark is opened in the online tab. Details of the opened bookmark shown in the status message.

    3. Test case: goto 0
      Expected: No bookmark is opened. Error details shown in the status message.

    4. Other incorrect goto commands to try: goto, goto x (where x is larger than the list size)
      Expected: Similar to previous.

F.1.3. Adding a bookmark to favorites

  1. Adding a Favorite tag to a bookmark while all bookmarks are listed

    1. Prerequisites: List all bookmarks using the list command. Multiple bookmarks in the list. The first bookmark does not contain Favorite tag.

    2. Test case: favorite 1 or fav 1
      Expected: A Favorite tag is added to the first bookmark and the bookmark also appears in the list of favorite bookmarks in the top right corner of the dashboard tab. Details of the bookmark shown in the status message.

    3. Test case: fav 0
      Expected: No bookmark is tagged Favorite. Error details shown in the status message.

    4. Other incorrect favorite commands to try: favorite, favorite x (where x is larger than the list size)
      Expected: Similar to previous.

F.1.4. Undoing previous commands

  1. Undoing the most recent undoable command

    1. Prerequisites: List all bookmarks using the list command. Multiple bookmarks in the list.

    2. Test case: delete 1 followed by undo
      Expected: The deleted bookmark after the first command re-appears in the bookmark list. Details of the delete action shown in the status message.

  2. Undoing multiple previous undoable commands

    1. Prerequisites: List all bookmarks using the list command. More than two bookmarks in the list.

    2. Test case: delete 1 followed by delete 1 and then undo 2
      Expected: The deleted bookmarks after the first two command re-appear in the bookmark list. Details of the two reverted delete actions shown in the status message.

    3. Test case: undo 0
      Expected: No action is undone. Error details shown in the status message.

    4. Other incorrect undo commands to try: undo, undo x (where x is larger than the maximum number of undoable actions)
      Expected: Similar to previous.

F.1.5. Redoing previous undo commands

  1. Redoing the most recent undo command

    1. Prerequisites: List all bookmarks using the list command. Multiple bookmarks in the list.

    2. Test case: delete 1 followed by undo and redo
      Expected: The deleted bookmark after the first command re-appears in the bookmark list. After the redo command, the deleted bookmark disappears from the bookmark list again. Details of the restored delete action shown in the status message.

  2. Redoing multiple previous undo commands

    1. Prerequisites: List all bookmarks using the list command. More than two bookmarks in the list.

    2. Test case: delete 1 followed by delete 1 and undo 2, then redo 2
      Expected: The two deleted bookmarks after the first two command re-appear in the bookmark list after the undo 2 command. After the redo 2 commands, the two bookmarks get deleted again. Details of the two restored delete actions shown in the status message.

    3. Test case: redo 0
      Expected: No action is redone. Error details shown in the status message.

    4. Other incorrect redo commands to try: redo, redo x (where x is larger than the maximum number of actions to redo)
      Expected: Similar to previous.

F.1.6. Exporting Mark data

  1. Exporting data to a valid file

    1. Prerequisites: None. However, the expected outcome is written with the assumption that mark.jar is opened from a folder named [applicationHome] (replace this with the name of the folder that mark.jar is in). (Also, note that if a file named myBookmarks.json already exists in the folder [applicationHome]/data/bookmarks/, it will be overwritten.)

    2. Test case 1 (success): export myBookmarks
      Expected outcome: A file named myBookmark.json is created in the folder [applicationHome]/data/bookmarks/. (You will have to use your File Explorer/ bash (Windows)/ Finder/ terminal (Mac) to confirm this.) The file is identical to the mark.json file in the folder [applicationHome]/data/. This can be checked by using the diff utility on your laptop’s command line, or copying-and-pasting both files into https://www.diffchecker.com/ to compare them.

  2. Other test cases you can copy-and-paste

    1. Test case 2 (error): export myBookmarks.json
      Expected outcome: The application shows an error message indicating that the provided file name should not include the .json file extension.

F.1.7. Importing bookmarks

You may have to manipulate data in Mark or in the exported .json files to observe different results when importing bookmarks.

  1. Attempting to import bookmarks from a file that does not contain new bookmarks

    1. Prerequisites: Test case 1 from the manual instructions for exporting data has been successfully completed. No changes to bookmarks have been made.

    2. Test case 1 (success): import myBookmarks
      Expected outcome: The application shows a message indicating that no new bookmarks have been imported. A list of duplicate bookmarks is also provided.

  2. Importing bookmarks from a file that contains some new bookmarks

    1. Prerequisites: A valid data file myBookmarks.json exists in the folder [applicationHome]/data/bookmarks. Some bookmarks have been deleted since export myBookmarks was executed (e.g. delete 4, delete 5, delete 6).

    2. Test case 2 (success): import myBookmarks
      Expected outcome: The deleted bookmarks have been added to the bottom of the bookmark list, and they are in the correct folders. A folder named ImportedBookmarks has also been created.

  3. Importing bookmarks into an empty Mark

    1. Prerequisites: Mark is empty. You can do this by executing the clear command. (Don’t worry, as long as you do not exit the application, you will be able to recover your data later using undo.) Also, a valid data file named myBookmarks.json should exist in the folder [applicationHome]/data/bookmarks.

    2. Test case 3 (success): import myBookmarks
      Expected outcome: All the bookmarks have been imported to the folder ImportedBookmarks.

F.2. Testing of folders

The ROOT folder is the top-level folder where all other folders are descendents of. It cannot be added, deleted, edited, or be renamed to.

Do use ROOT as a folder name in folder commands in testing.

F.2.1. Expanding the folder hierarchy

  1. Expanding the folder hierarchy all the way

    1. Prerequisites: There is at least one folder not expanded.

    2. Test case: expand 9999 (assuming you have less than 9999 levels of folders)
      Expected: The folder hierarchy should show all folders expanded.

  2. Expanding the folder hierarchy while there is nothing left to expand

    1. Prerequisites: Expand the folder hierarchy all the way, using expand 9999 as in the previous test case.

    2. Test case: expand / expand 10 / expand 9999
      Expected: The folder hierarchy should not change.

F.2.2. Collapsing the folder hierarchy

  1. Collapsing the folder hierarchy all the way

    1. Prerequisites: There is at least one folder expanded.

    2. Test case: collapse 9999 (assuming you have less than 9999 levels of folders)
      Expected: The folder hierarchy should show all folders collapsed.

  2. Collapsing the folder hierarchy while there is nothing left to collapse

    1. Prerequisites: Collapse the folder hierarchy all the way, using collapse 9999 as in the previous test case.

    2. Test case: collapse / collapse 10 / collapse 9999
      Expected: The folder hierarchy should not change.

F.2.3. Adding folders

  1. Adding an existing folder

    1. Prerequisites: At least one folder exists in Mark. Let the name of the folder be X.

    2. Test case: folder X / folder X p/ROOT
      Expected: An error occurs, saying X already exists.

  2. Adding a folder without specifiying its parent

    1. Prerequisites: A folder named X that does not exist in Mark.

    2. Test case: folder X
      Expected: Folder X is added successfully to the ROOT folder.

F.2.4. Editing folders

  1. Editing a non-existing folder

    1. Prerequisites: A folder named X that does not exist in Mark.

    2. Test case: folder-edit X t/anything
      Expected: An error occurs, saying X does not exist.

  2. Renaming an existing folder to another existing folder

    1. Prerequisites: Two different folders named X and Y that exist in Mark.

    2. Test case: folder-edit X t/Y / folder-edit Y t/X
      Expected: An error occurs, saying that X (or Y) already exists.

F.2.5. Deleting folders

  1. Deleting a folder containing other bookmarks and/or folders

    1. Prerequisites: A folder named X that contains other subfolders.

    2. Test case: folder-delete X
      Expected: An error occurs, saying that there are still bookmarks/folders in X and so cannot be deleted.

F.4. Testing of annotations

  1. Adding an annotation to a paragraph of web page

    1. Prerequisites: Bookmark must have an offline copy available. You can check the bookmark list to see if an offline copy is available. The offline copy should also have content to annotate.

    2. Test case: annotate 1 p/p1 n/added a note h/orange
      Expected: Offline copy of the first bookmark is annotated. The first paragraph is highlighted orange and a note "adding a note" is added on the right of the paragraph. Details of the annotation added can be found in the status message.

    3. Test case: annotate 0 p/p1
      Expected: No annotation is made. Error details are shown in the status message.

    4. Test case: annotate 1 p/incorrectIdentifier eg. 1
      Expected: No annotation is made. Error details are shown in the status message.

  2. Adding a general note to the offline copy of a bookmark

    1. Prerequisites: Bookmark must have an offline copy available.

    2. Test case: annotate 1 p/null n/added a general note
      Expected: Offline copy of the first bookmark is annotated. A general note "added a general note" is added to the bottom of the document.

    3. Test case: annotate 1 p/null
      Expected: No annotation is made. Error details are shown in the status message.

  3. Deleting all annotations of an offline copy

    1. Prerequisites: Bookmark must have an offline copy available.

    2. Test case: annotate-delete 1 p/all
      Expected: Offline copy of the first bookmark is clear of any annotations.

    3. Test case: annotate-delete 1 p/all n/anything
      Expected: Offline copy of the first bookmark is clear of any annotations.

  4. Deleting only the highlight of a paragraph of an offline copy

    1. Prerequisites: Bookmark must have an offline copy available. Paragraph chosen must be annotated.

    2. Test case: annotate-delete 1 p/p1 n/true
      Expected: Annotation on the offline copy of the first bookmark is affected. Paragraph 1 is no longer annotated. If paragraph 1 had a note, it is now found at the bottom of the page.

  5. Deleting only the note of a paragraph of an offline copy

    1. Prerequisites: Bookmark must have an offline copy available. Paragraph chosen must have a note.

    2. Test case: annotate-delete 1 p/p1 h/true
      Expected: Annotation on the offline copy of the first bookmark is affected. Paragraph 1 no longer has a note.

  6. Deleting the annotation of a paragraph of an offline copy (in other cases)

    1. Prerequisites: Bookmark must have an offline copy available. Paragraph chosen must be annotated.

    2. Test case: annotate-delete 1 p/p1
      Expected: Annotation on the offline copy of the first bookmark is affected. Paragraph 1 is no longer annotated.

    3. Test case: annotate-delete 1 p/p1 n/true h/true
      Expected: No annotation is deleted. Error details are shown in the status message.

  7. Moving an annotation from paragraph to paragraph of an offline copy

    1. Prerequisites: Bookmark must have an offline copy available. Paragraph to move annotation from must be annotated. Target paragraph must also exist and its id must not start with G.

    2. Test case: annotate-edit 1 p/p1 to/p2
      Expected: Annotation on the offline copy of the first bookmark is affected. The annotation of paragraph 1 is moved to paragraph 2.

    3. Test case: annotate-edit 1 p/p1 to p/g1
      Expected: No annotation is edited. Error details are shown in the status message.

  8. Editing the content of an annotation of an offline document

    1. Prerequisites: Bookmark must have an offline copy available. Paragraph chosen must be annotated.

    2. Test case: annotate-edit 1 p/p1 n/modify note h/green
      Expected: Annotation on the offline copy of the first bookmark is affected. Paragraph 1 is now highlighted green and the content of its note is now "modify note".

F.5. Testing of autotags

F.5.1. Creating an autotag

General prerequisites: Sample Mark data are being used. Specific requirements for each test case are elaborated below.

  1. Adding autotags that do not immediately tag any bookmarks.

    1. Prerequisites: find t/Hidden shows 0 bookmarks. There is no autotag named Hidden.

    2. Test case 1 (success): autotag Hidden f/Secret
      Expected outcomes: The autotag Hidden can be seen in the autotag panel on the Dashboard. Searching find t/Hidden results in 0 bookmarks being listed. Creating a folder named Secret (e.g. folder Secret) and adding a bookmark to the folder (e.g. add n/Nope u/https://testingSecret.com f/Secret) results in the new bookmark being tagged with Hidden.

  2. Adding autotags that immediately tag bookmarks.

    1. Prerequisites: find t/readLater shows 0 bookmarks. There are 3 bookmarks with URLs containing blog or medium.

    2. Test case 2 (success): autotag readLater u/blog u/medium nf/School
      Expected outcome: The autotag readLater is added. find t/readLater shows 3 bookmarks, all of which have been tagged readLater. Adding a bookmark using add n/Added Autotag1 u/https://blogger.org results in the new bookmark being tagged, but the bookmark created by inputting add n/Added Autotag2 u/https://blogspot.com f/School is not tagged.

  3. Trying to add autotags that already exist. Error messages will be shown.

    1. Prerequisite: An autotag named NUS already exists.

    2. Test case 3 (error): autotag NUS u/nus.edu.sg n/School f/School
      Expected outcome: The application shows an error message indicating that an autotag with the given name already exists.

  4. Other test cases that you can copy-and-paste:

    1. Test case 4 (success): autotag Bouncy n/Ball u/tennis nf/Dance

    2. Test case 5 (success): autotag Quiz f/NUS f/Module nu/github nu/stackoverflow

    3. Test case 6 (success): autotag LumiNUS u/luminus.nus.edu.sg nf/Miscellaneous

    4. Test case 7 (error): autotag asdfjkl; n/Help
      Expected outcome: The applications shows an error message indicating that tag names must be alphanumeric.

    5. Test case 8 (error): autotag noConditions
      Expected outcome: The applications shows an error message indicating that at least one condition must be specified.

F.5.2. Editing an autotag

  1. Editing an autotag’s name

    1. Prerequisite: An autotag named Video already exists, but no autotag named Videos exists. The autotag Video tags bookmarks with URLs containing youtube.com.

    2. Test case 1 (success): autotag-edit Video t/Videos
      Expected outcome: The autotag’s name has been changed from Video to Videos. Bookmarks that match the original autotag conditions (i.e. URLs contain youtube.com) have been tagged Videos. None of the original tags were removed.

  2. Editing an autotag’s conditions

    1. Prerequisite: An autotag named NUS already exists.

    2. Test case 2 (success): autotag-edit NUS n/School n/Uni n/NUS
      Expected outcome: One autotag named NUS is shown on the Dashboard. The autotag conditions specify that bookmarks with names containing School, Uni, or NUS will be tagged.

F.5.3. Deleting an autotag

  1. Deleting an autotag that already exists

    1. Prerequisite: An autotag named Videos already exists. (For example, if you followed test case 1 in the testing instructions for autotag-edit.)

    2. Test case 1 (success): autotag-delete Videos
      Expected outcome: The autotag Videos has been deleted and is no longer visible on the Dashboard. However, no bookmarks have been changed (you can verify this using find t/Videos).

  2. Trying to delete an autotag that does not exist

    1. Prerequisite: No autotag named KittyKat exists.

    2. Test case 2 (error): autotag-delete KittyKat
      Expected outcome: The application shows an error message indicating that no autotag with the given tag name was found.

  3. Other test cases that you can copy-and-paste:

    1. Test case 3 (error): autotag-delete Videos some random stuff

    2. Test case 4 (error): autotag-delete Videos t/HopingToRenameIt

F.6. Testing of reminders

F.6.1. Adding a reminder

  1. Adding a reminder while all bookmarks are listed

    1. Prerequisites: List all bookmarks using the list command. Multiple bookmarks in the list.

    2. Test case: reminder 1 t/12/12/2030 1200
      Expected: The reminder of the first bookmark is added into reminder list.

    3. Test case: reminder 0 t/12/12/2030 1200
      Expected: No reminder is added into the reminder list. Error details shown in the status message. Status bar remains the same.

    4. Test case: reminder 1 t/30/02/2030 1200
      Expected: No reminder is added into the reminder list. Error details shown in the status message. Status bar remains the same.

F.6.2. Editing a reminder

  1. Editing a reminder

    1. Prerequisites: The reminder list has 5 reminder.

    2. Test case: reminder-edit 1 n/Read
      Expected: The note of the first reminder changes to "Read".

    3. Test case: reminder-edit 0 n/Read
      Expected: No reminder is edited in the reminder list. Error details shown in the status message. Status bar remains the same.

    4. Test case: reminder-edit 6 n/Read
      Expected: No reminder is edited in the reminder list. Error details shown in the status message. Status bar remains the same.

F.6.3. Deleting a reminder

  1. Deleting a reminder

    1. Prerequisites: The reminder list has 5 reminder.

    2. Test case: reminder-delete 1
      Expected: The first reminder in the list is deleted.

    3. Test case: reminder-delete 0
      Expected: No reminder is deleted in the reminder list. Error details shown in the status message. Status bar remains the same.

    4. Test case: reminder-delete 6
      Expected: No reminder is edited in the reminder list. Error details shown in the status message. Status bar remains the same.

F.6.4. Opening the bookmark associated with a reminder

  1. Opening a bookmark using a reminder

    1. Prerequisites: The reminder list has 5 reminder.

    2. Test case: reminder-goto 1
      Expected: The first reminder in the list is opened.

    3. Test case: reminder-goto 0
      Expected: No website will show. Error details shown in the status message. Status bar remains the same.

    4. Test case: reminder-goto 6
      Expected: No website will show. Error details shown in the status message. Status bar remains the same.