By: Team W17-2
Since: 14 Mar 2019
Licence: MIT
- 1. Setting up
- 2. Design
- 3. Implementation
- 4. Documentation
- 5. Testing
- 6. Dev Ops
- Appendix A: Suggested Programming Tasks to Get Started
- Appendix B: Product Scope
- Appendix C: User Stories
- Appendix D: Use Cases
- Appendix E: Non Functional Requirements
- Appendix F: Glossary
- Appendix G: Instructions for Manual Testing
-
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 JDK9
. -
IntelliJ IDE
ℹ️IntelliJ by default has Gradle and JavaFx plugins installed.
Do not disable them. If you have disabled them, go toFile
>Settings
>Plugins
to re-enable them.
-
Fork this repository, and clone the fork to your computer
-
Open IntelliJ (if you are not in the welcome screen, click
File
>Close Project
to close the existing project dialog first) -
Set up the correct JDK version for Gradle
-
Click
Configure
>Project Defaults
>Project Structure
-
Click
New…
and find the directory of the JDK
-
-
Click
Import Project
-
Locate the
build.gradle
file and select it. ClickOK
-
Click
Open as Project
-
Click
OK
to accept the default settings -
Open a console and run the command
gradlew processResources
(Mac/Linux:./gradlew processResources
). It should finish with theBUILD SUCCESSFUL
message.
This will generate all resources required by the application and tests. -
Open
MainWindow.java
and check for any code errors-
Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully
-
To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select
Add '--add-modules=…' to module compiler options
for each error
-
-
Repeat this for the test folder as well (e.g. check
HelpWindowTest.java
for code errors, and if so, resolve it the same way)
-
Run the
seedu.address.MainApp
and try a few commands -
Run the tests to ensure they all pass.
This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,
-
Go to
File
>Settings…
(Windows/Linux), orIntelliJ IDEA
>Preferences…
(macOS) -
Select
Editor
>Code Style
>Java
-
Click on the
Imports
tab to set the order-
For
Class count to use import with '*'
andNames count to use static import with '*'
: Set to999
to prevent IntelliJ from contracting the import statements -
For
Import Layout
: The order isimport static all other imports
,import java.*
,import javax.*
,import org.*
,import com.*
,import all other imports
. Add a<blank line>
between eachimport
-
Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.
After forking the repository, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level4
repository.
If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level4
), you should do the following:
-
Configure the site-wide documentation settings in
build.gradle
, such as thesite-name
, to suit your own project. -
Replace the URL in the attribute
repoURL
inDeveloperGuide.adoc
andUserGuide.adoc
with the URL of your fork.
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) |
When you are ready to start coding,
-
Get some sense of the overall design by reading Section 2.1, “Architecture”.
-
Take a look at Appendix A, Suggested Programming Tasks to Get Started.
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.
The following class plays an important role at the architecture level:
-
LogsCenter
: Used by many classes to write log messages to the App’s log file.
The rest of the App consists of four components.
Each of the four components
-
Defines its API in an
interface
with the same name as the Component. -
Exposes its functionality using a
{Component Name}Manager
class.
For example, the Logic
component (see the class diagram given below) defines it’s API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class.
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
The sections below give more details of each component.
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, 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. -
Listens for changes to
Model
data so that the UI can be updated with the modified data.
API :
Logic.java
-
Logic
uses theAddressBookParser
class to parse the user command. -
This results in a
Command
object which is executed by theLogicManager
. -
The command execution can affect the
Model
(e.g. adding a person). -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
. -
In addition, the
CommandResult
object can also instruct theUi
to perform certain actions, such as displaying help to the user.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API call.
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores the Address Book data.
-
exposes an unmodifiable
ObservableList<Person>
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. -
does not depend on any of the other three components.
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. -
can save the Address Book data in json format and read it back.
This section describes some noteworthy details on how certain features are implemented.
The Patient
class represents patients for our users on TeethHub.
It extends Person
with more patient-specific attributes, as well as methods.
Various methods are also overridden in order for them to work appropriately with the new Patient
class.
The following class diagram summarizes the new Patient
class, which extends from Person
:
The Record
class represents a dental record of a patient. Each Patient
class has an list of Record
as an attribute.
The Record
class is purposely implemented to be similar to that of Person
.
Just like person, record has associate classes for specific operations, such as storage.
This ensures that the processing of records is streamlined with Patient
, which extends Person
.
The current implementation to view a specified patient’s dental records uses the goto
command.
The GoToCommand
extends the Command
abstract class.
The valid form of the command is goto INDEX
.
The INDEX
of the command specifies the patient in the patient list, based to their denoted indexes.
On the other hand, the current implementation to go back to the patient list uses the back
command.
It also extends the Command
abstract class.
Unlike the GoToCommand
, the BackCommand
does not take in any parameters.
The valid form of the command is back
.
Given below is an example usage scenario and how the goto/back mechanism behaves at each step.
Step 1. The user launches the application for the first time. All stored patients will be loaded and the user will be shown the patient list by default.
Step 2. The user executes goto 1
command to view the dental records of the first patient in the dental book.
The goto
command sets the specified patient in the MainWindow as the first patient.
The patient list is now replaced by the dental record list of the specified patient.
Step 3. The user can now add, edit, or delete dental records, which are tied to the specified patient.
ℹ️
|
If the goto command is entered while the window is already showing dental records of a specified patient, an error message will be displayed on the window.
|
Step 4. The user now decides that he wants to view the patient list.
He do so by executing the back
command.
After which, the record list is replaced by the patient list.
ℹ️
|
The back command will still work with parameters, but those parameters will be ignored.
|
Step 5. The user can now add, edit, or delete patients' personal information.
ℹ️
|
If the back command is entered while the window is already showing patients, an error message will be displayed on the window.
|
The following activity diagram summarizes what happens when a user executes the goto
or back
command:
The Teeth
class represents patients' teeth for our users on TeethHub.
It consist of an array of Tooth
objects, which represents the individual tooth of patients.
ℹ️
|
When a patient is added by the user, TeethHub automatically creates a new set of all healthy and present teeth for the new patient. At this point of time, only permanent teeth is supported. |
Each Tooth
can be present or absent. If it is present, it can be on or off status.
A tooth on status would mean that it is a problematic tooth (i.e. decaying tooth or dental prosthesis).
A optional status message can be tied to each tooth on status, allowing our users to have a better overview of the tooth if necessary.
The command to edit a specific tooth of a patient is: teethedit INDEX
.
ℹ️
|
The teethedit command can only run after a patient is specified via the goto command.
|
The following class diagram summarizes the Teeth
class, which is a composition of the Tooth
class:
There are only two types of tags that are valid in the Patient
class.
They are StatusTag
and TeethTag
.
At any time, there can only be one of each kind of those tags.
Unlike in Person
, users cannot added their own tags to patients.
We have implemented patient tags to be fully automated by our application.
StatusTag
: Describes the condition of the patient’s teeth. The valid options are "Healthy Teeth", "Status Teeth", and "Absent Teeth".
The worst tooth status found in the patient’s teeth will be reflected in the teeth status tag.
The activity diagram below demonstrates how TeethHub automatically update the patient’s status tag when his or her tooth is edited:
TeethTag
: Describes the teeth type of the patient. The valid options are "Primary Teeth" or "Permanent Teeth".
ℹ️
|
Since only permanent teeth is supported by TeethHub at the moment, TeethTag will always show "Permanent Teeth".
|
Following the single user policy, TeethHub only prompts the user once to acquire his or her name, which will then be used when creating new dental records for patients.
Currently, the application prompts the user for his or her name during his or her first attempt when adding a new dental record to a specified patient via the RecordAdd
command.
ℹ️
|
Currently, the dentist’s name is stored in a .txt file in TeethHub.
It is possible for users to change their name from the .txt file, although they are not encouraged to do so.
|
We have also created new commands that will facilitate the Patient
class.
ℹ️
|
The commands below can only be executed after a patient is specified via the GoTo command.
|
RecordAdd
: Adds a new record to a specified patient.
RecordEdit
: Edits an existing record of a specified patient.
RecordDelete
: Deletes an existing record of a specified patient.
RecordClear
: Clear all records of a specified patient.
-
Current implementation: Create the
Patient
class by extending it fromPerson
.-
Alternative: Create the
Patient
class from the bottom-up.-
Alternative Pros: As
Patient
will not be a subclass of any other class, it will be less affected by changes in other classes. -
Alternative Cons: All existing classes and methods which currently work with
Person
needs to be re-written to work with the newPatient
class. Attributes and methods cannot be reused, and must be re-implemented. Lastly, polymorphism cannot be applied in cases where there is a need to deal with both persons and patients.
-
-
Choice Justification: It is intuitive, as it is logical that all patients are persons as well. The code from
Person
can be reused inPatient
through inheritance, and all existing classes and methods which work withPerson
will also work withPatient
. Most importantly, it allows us to make use of the object-oriented programming principles we learnt in class. We assume that the Open-Closed Principle is applied on thePerson
class.
-
-
Current implementation: Store the records using a list.
-
Alternative: Store the records using a hash table.
-
Alternative Pros: It gives the fastest time complexity if a record search is required. It runs in O(1) time.
-
Alternative Cons: It can be challenging for collaborators to understand, which can be detrimental for collaborative programming. It also creates an extra layer of complexity in order to display the stored records in the order they were first stored in the application by our users.
-
-
Choice Justification: It is easy to understand, which is crucial for collaborative programming as other programmers may require accessing the records in the list. It can also save new records in the order of when they are added, from most recent to oldest, simply using
List.add(0, Record)
. However, if the list gets long over time, it might cause additional waiting time for our users when they would to search for a specific record. This is because a linear search in a list is of O(n) time complexity.
-
-
Current implementation: Use a static variable to store the specified patient, with a public getter method, and a static boolean that denotes the current list viewing mode.
-
Alternative: Save the specified patient and list viewing mode as an instance variable of MainWindow.
-
Alternative Pros: Will work properly even if MainWindow is no longer a singleton class.
-
Alternative Cons: Challenging to implement as major revamp is required to most existing classes and tests. All new classes which wish to access the specified patient or list viewing mode will need to take in a reference to the MainWindow instance.
-
-
Choice Justification: This is relatively easy to implement and understand. Furthermore, other classes can easily access the current specified patient, and the current list viewing mode. However, it may cause complications if MainWindow is no longer a singleton class.
-
-
Current implementation: Use a patient variable to store the patient specified by the command.
-
Alternative: Create a new immutable patient variable to store the specified patient.
-
Alternative Pros: Ensures the the specified patient cannot be edited by other classes or methods.
-
Alternative Cons: Major changes to the patient class would require the immutable patient class to be changed too. Furthermore, every time any record of the specified patient is modified, a new immutable patient needs to be created to update the currently stored immutable patient.
-
-
Choice Justification: An intuitive solution, as the specified patient is stored as an exact same class as a patient. Additionally, attributes of the specified patient can be accessed just like any other patient class. Changes to the patient class does not significantly affect the goto command. However, the patient class is mutable, and accidental changes to its attributes by other classes or methods can occur.
-
-
Current implementation: Create a
Tooth
object representing a tooth, and use an array to store a list of tooth which will represent the teeth of patients.-
Alternative: Create an integer array representing teeth. Each integer value in the array indicates the status of a tooth.
-
Alternative Pros: Simplest to implement.
-
Alternative Cons: Can be hard to understand by other programmers as integers are used to represent teeth statuses. Additionally, this is violating object-oriented principles.
-
-
Choice Justification: An straightforward object-oriented solution and easy to understand by other collaborating programmers who are familiar with object-oriented programming. However,
Tooth
andTeeth
objects, as well as their relevant methods takes a significant amount of time to be created. They will also require proper test cases to be implemented.
-
-
Current implementation: Copies are not serialized into json file. It’s only inside the memory. When copies exist before exit, program will ask for user’s confirmation
-
Alternative: Serialize copies into json file. Make changes to comparators of patient/task such that it accepts same entries with copy tag.
-
Alternative Pros: Everything is on track. User does not need to make a confirmation before exiting with copies. And if the program exits accidentally, the copies are still there.
-
Alternative Cons: User may be able to generate too many unnecessary entries easily, whose serialization is costly in time and space. The change of comparator also violates the design of unique list, bringing potential security issues as well.
-
-
Choice Justification: Serialization may need less code in the beginning. The maintenacne is more difficult, and the robustness of the program is compromised. On the other hand, creating copies for the user is not a hard task. Thus, creating copies on demand should be a better idea.
-
Our user can also utilize TeethHub to store manage and interact with tasks aside from patients. Dentists would often have tasks that needs to be completed or performed and the implementation of the Task
data object allows TeethHub to provide its user with the ability to manage these tasks and have them interact with their patient’s details when necessary as well. The main commands added with the data object serve to provide the user with a way to manage and view the tasks stored in the application.
This segment will explain the main features of the a new data type Task
that TeethHub handle. Key implementation details and the design considerations when designing these features will also be covered.
The Task
class is a new type of object that is meant to represent any task a dentist would have to perform. Task
objects are kept in a list and handled by the UniqueTaskList
class. This follows the same process as the Patient
class and by extension, the Person
class which has a UniquePersonList
handling a list of Person
objects.
Storing information of all objects does not change between Task
and Person
classes and both are stored into a TeethHub.json
file. Attributes for Task
are instantiated as objects with their own methods to validate the user’s input much like the current implementation of Patient
/Person
.
ℹ️
|
Task has an attribute called LinkedPatient which stores the NAME and NRIC of a patient. This is to allow tasks to be bound to a patient and facilliate another implementation taskdone .
|
The following class diagram details the attributes Task
and the multiplicity association between the classes:
Essential commands for basic interaction with the tasks such as, taskadd
, taskedit
and taskdelete
are implemented in a similar fashion as compared to existing commands. All three commands extend from the abstract class Command
and follow the same execution flow as patientadd
, patientedit
and patientdelete
.
The generic execution flow of these commands mostly involve the Logic
and Model
component of the application and the order can be detailed as follows:
-
User’s input command gets sent to LogicManager.
-
LogicManager sends to AddressBookParser to check command validity.
-
AddressBookParser creates a CommandParser(typically named
commandclassnameParser
likeTaskAddCommandParser
) to check the arguments. -
CommandParser creates an instance of the Command and sends it back to LogicManager.
-
LogicManager calls the method to execute the Command.
-
Command executes and calls methods from Model to interact with the
Task
objects. -
Result from the command’s execution gets sent back to the UI and eventually the user.
ℹ️
|
Adding and editing tasks may involved additional calls to Model if the user specifies a patient to link to the task as the application will have to extract unique information from the Patient to store in the task
|
The following sequence diagram illustrates the stated generic command execution flow and the components involved:
The taskdone
command is a feature implemented to allow the user to automatically complete a task of his/her choice, setting it to COMPLETED
. The command also allows the user to automatically add a record to the task’s LinkedPatient
upon completion of the task. This feature makes use of the Model
by calling its methods setTask()
and setPerson()
to perform additional interactions with both task and patient respectively.
The general process followed during the command’s execution is as follows:
-
User runs a valid
taskdone
command on a task with a linkedpatient -
Command gets parsed properly and reaches the execution stage within
TaskDoneCommand
-
New task gets created with the exact same attributes as the task to be completed.
-
New task is set to COMPLETED
-
setTask method is called to replace the task to be completed with the new task
-
TeethHub’s stored list of patients is searched to look for a patient matching the task’s linked patient’s NRIC
-
Retrieves the found patient and adds a record to the patient.
-
setPerson method is called to replace the matching patient with the new patient who has the added record.
-
Command execution ends and result is returned back to the user.
ℹ️
|
The current patientedit and patientdelete commands have been modified to update tasks linked to a patient when said patient gets modified or deleted. However, to keep in line with the concept of Defensive Programming, the execution still check and inform the user in an event where the LinkedPatient cannot be found.
|
The following sequence diagram illustrates an example where command executes on task with the index 1
:
The taskcal
command is a feature implemented to allow the user to view task related data in a more interactive and helpful manner. Executing the command creates a popup window with the list of tasks on the left side and a calendar on the right side. The popup window will also have a commandbox where the user is able to key in task related commands, dates in the format of dd-mm-yyyy
to navigate the calendar or exit and help as miscelleanous commands.
This feature also enables the user to find tasks depending on the date
they provide in the commandbox or the date cell they click on such that the task list will update to show users the tasks with start dates and/or end dates on the selected date cell.
ℹ️
|
Closing the Main Window whilst the Task Calendar popup is showing will also close the popup automatically. This is implemented intentionally as the popup is designed to be an extension of the Main Window, not a standalone window. |
The calendar is generated with modifications to the date cells being done during the generation process. Modifications to the date cells (e.g. changing font colors, overlaying a circle) are done to highlight date cells with the color of task priority.
The class CalendarWindow
uses a HashMap
with the date, as the key and priority number, as a value,to store the start date and end date information from tasks stored in TeethHub, replacing the stored value with a highest priority value between the duplicates. This enables the date cells to be compared with the stored values amidst the generation process, choosing which date cells to highlight with the highest priority color.
The UI components of this feature are implemented using classes from JavaFX
with the most important component being DatePicker
which allows a calendar to be drawn up and displayed to the user. The implementation overrides the updateItem()
method from DatePicker
to enable modifications to be done to the date cells in the calendar.
ℹ️
|
Color scheme and certain font sizes of the Calendar are done using Cascading Style Sheets (CSS) in a Calendar.CSS file found under resources. Changes can be made to the CSS file to change color schemes of the calendar if needed
|
The following swimlane activity diagram illustrates the process in which the application goes through to create and display the interactive calendar for the user:
-
Current implementation: Tasks are stored in the same json file as patients.
-
Alternative: Store the tasks in a seperate json file
-
Alternative Pros: Very unlikely to have a very large json file by saving different data types to a different file.The single json file that stores the data will not become too large if there are too many patients and/or tasks. Reduces the likelihood that the size of the json file will exceed the limit of certain file systems like FAT-32 with a limit of 4GB, which would result in the application being unable to save to the file.
-
Alternative Cons: Increases time spent opening a seperate json file. This results in more I/O work for application, decreasing application load time.Complicated to implement and affects future storage and I/O features. Implementation would affect I/O features that may need to read from the files to export both patients and tasks. Sets a precedent where new data types added in the future should be stored seperately for consistency, which would affect
export
andimport
commands as they have to be changed to read a new file to export/import.
-
-
Choice Justification: Most operating systems these days utilize a filesystem that allows for filesizes far beyond 4GB. The limit can go up to 2^64 bytes in NTFS for example and in almost all cases, it is unlikely for the a dentist to have such a large number of patients and tasks. Compared to the cons of the alternative where performance and increased likelihood of issues in the futre, the pros that the alternative offers do not seem valid enough to justify using it over the current choice.
-
-
Current implementation: Java’s special Enumeration (Enum) class is used for the Priority class
-
Alternative: Use a normal class type, with string as inner attribute for Priority.
-
Alternative Pros: Priorities can be named anything the user desires such as "code red", "code blue" for example. Easy to implement as it functions similarly to how
name
andtitle
works. -
Alternative Cons: Priority tracking depends on the user’s own memory of what he/she named them and sees as a higher priority, making it difficult for the program to display the appropriate color for priorities to benefit users. Goes against good programming practice which is to use enum for values that should not be changed and Enum is typically used to store priority types.
-
-
Choice Justification: Priorities being represented as "high", "med" and "low" is a common case and as such, most if not all people should be able to understand the naming convention. While giving freedom to the user is typically beneficial, in this case, it prevents the application from providing better more value to the user. The current choice uses terms that are understandable whilst providing benefits to the user by enabling a consistent colored display of priority tags.
-
-
Current implementation: Target task and patient are replaced with new instances with priority changed and record added respectively.
-
Alternative: Modify the target task and linked patient directly.
-
Alternative Pros: Easy to implement as they already have methods to perform the operations Requires less operations to execute the command, leading to performance increase.
-
Alternative Cons: Completing a task does not work with undo and redo due to the way the model is implemented.
-
-
Choice Justification: The current choice enables undo and redo to work with the task autocomplete feature, enabling tasks to be un-completed by using the undo command. This is important as the user could potentially key the wrong index when completing a task and wish to undo the command without having to manually edit the task. From tests done with the alternative, the performance increase is also negligible at best. The lack of a strong benefit does not make up for the inability to undo/redo making the alternative less desirable as compared to the current choice.
-
-
Current implementation: HashMap is used to store dates that have task’s starting and/or ending.
-
Alternative: Use an ArrayList to store the dates instead.
-
Alternative Pros: Does not require its stored values to have an
equals()
method. Lower memory consuption as there is no need to store both a key and a value. Retrieves elements withget()
in O(1) time with a specified index. -
Alternative Cons: Checking if an element exists within is O(n) time as the entire arraylist needs to be checked. Requires knowing the actual index in order to utilize the benefit of an O(1) retrieval.
-
-
Choice Justification: The generation of the calendar page requires a data structure that can check if a particular date is contained within itself in order to decide whether a date cell should be highlighted or not. Arraylist would be slower in this case as it would have to peform an O(n) every time a task gets iterated compared to the O(1) a HashMap offers. There is also no need to implement an
equals()
method for the DateCustom object to be stored as it is already implemented to ensure that there are no duplicate tasks. In this case, the current choices is a much better fit as compared to arraylist to support the calendar feature.
-
-
Current implementation: Task Calendar window is displayed in a seperate popup.
-
Alternative: Display the calendar in another pane in the Main Window.
-
Alternative Pros: Everything can be shown at one glance to the user. No need to shuffle between windows. No need for a command to make the calendar show.
-
Alternative Cons: Other elements displayed in the Main Window have to be shrunk along with the calendar’s contents to make space for the task calendar to be viewed. Main Window would be displaying too many things.
-
-
Choice Justification: Shrinking all elements in the Main Window would adversely affect the visibility of other important features the application provdies such as the patient’s information, the list of patients and list of tasks. This reduces the user experience as content cannot be clearly seen. Following the traditional usage of a calendar, most people typically look at a calendar when the need arises such as planning or scheduling. It would be better for a command to be provided and show the user a clear and distinct calendar only when he/she requires it, making the current choice ideal.
-
The undo/redo mechanism is facilitated by VersionedAddressBook
.
It extends AddressBook
with an undo/redo history, stored internally as an addressBookStateList
and currentStatePointer
.
Additionally, it implements the following operations:
-
VersionedAddressBook#commit()
— Saves the current address book state in its history. -
VersionedAddressBook#undo()
— Restores the previous address book state from its history. -
VersionedAddressBook#redo()
— Restores a previously undone address book state from its history.
These operations are exposed in the Model
interface as Model#commitAddressBook()
, Model#undoAddressBook()
and Model#redoAddressBook()
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 VersionedAddressBook
will be initialized with the initial address book state, and the currentStatePointer
pointing to that single address book state.
Step 2. The user executes delete 5
command to delete the 5th person in the address book. The delete
command calls Model#commitAddressBook()
, causing the modified state of the address book after the delete 5
command executes to be saved in the addressBookStateList
, and the currentStatePointer
is shifted to the newly inserted address book state.
Step 3. The user executes add n/David …
to add a new person. The add
command also calls Model#commitAddressBook()
, causing another modified address book state to be saved into the addressBookStateList
.
ℹ️
|
If a command fails its execution, it will not call Model#commitAddressBook() , so the address book state will not be saved into the addressBookStateList .
|
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#undoAddressBook()
, which will shift the currentStatePointer
once to the left, pointing it to the previous address book state, and restores the address book to that state.
ℹ️
|
If the currentStatePointer is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The undo command uses Model#canUndoAddressBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.
|
The following sequence diagram shows how the undo operation works:
The redo
command does the opposite — it calls Model#redoAddressBook()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the address book to that state.
ℹ️
|
If the currentStatePointer is at index addressBookStateList.size() - 1 , pointing to the latest address book state, then there are no undone address book states to restore. The redo command uses Model#canRedoAddressBook() 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 address book, such as list
, will usually not call Model#commitAddressBook()
, Model#undoAddressBook()
or Model#redoAddressBook()
. Thus, the addressBookStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitAddressBook()
. Since the currentStatePointer
is not pointing at the end of the addressBookStateList
, all address 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.
The following activity diagram summarizes what happens when a user executes a new command:
-
Current implementation: Saves the entire address book.
-
Alternative: Individual command knows how to undo/redo by itself.
-
Alternative Pros: Will use less memory (e.g. for
delete
, just save the person being deleted). -
Alternative Cons: We must ensure that the implementation of each individual command are correct.
-
-
Choice Justification: We decided to go with the current implementation as it is simple and easier to fix.
-
-
Current implementation: Use a list to store the history of address book states.
-
Alternative: Use
HistoryManager
for undo/redo-
Alternative Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. Logic is not duplicated twice as a result.
-
Alternative 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. It is also harder for the new incoming developers of our project, who are new Computer Science student undergraduates, to understand.
-
-
Choice Justification: We decided to keep to the Single Responsibility Principle and Separation of Concerns, thus we chose the current implementation.
-
The Find mechanism is facilitated through the use of predicates in conjunction with the
FilteredList
within ModelManager
The below sequence diagram gives an overview of how a Find
Command is created. When the proper string arguments for
a Find Command is entered into the user interface, the arguments are passed to the Logic Manager and then the
AddressBook parser to determine if the input is valid. If it valid, a new FindCommandParser
object is then created
and handles the remaining user input.
From FindCommandParser, it creates a MultipleContainsKeywordsPredicate
before going into a loop that creates the
respective parameter ContainsKeywordsPredicate
if the parameter is present within the user input. The newly created
ContainsKeywordsPredicate
objects are passed back to the FindCommandParser
and stored within a list. Once the
loop has finished the list is passed over to the MultipleContainKeywordsPredicate
object before it is used in the
creation of a FindCommand
object. On success, the FindCommand
object is returned to the LogicManager as per the
flow in the sequence diagram.
On execution of the Find Command, updateFilteredPersonList
is called with the predicate stored within the program
itself. It subsequently calls setPredicate(predicatte)
and updates the displayed person list to only show patients
that matches the given predicate.
-
Current Implementation: Every associated parameter that is found within either a
Patient
orRecord
class has an associated parameter predicate classes extended fromContainsKeywordsPredicate
class that handles the predicate testing when that parameter is specified to be searched.
(E.g. The predicate for Name is handled inNameContainsKeywordsPredicate
)-
Alternative 1:
Patient
andRecord
would each have an individually associatedPatientContainsKeywordsPredicate
andRecordContainsKeywordsPredicate
that contains each respective parameter predicate class within itself.-
Alternative Pros: Collates all parameter predicates relevant to either Patient or Record within a singular class. This makes it easier for future developers to discover which file to change should they require to add or remove additional parameter predicates from TeethHub.
-
Alternative Cons: Violates Open-Closed Principle as every time a parameter predicate needs to be added or modified, we are forced to modify the code of either
PatientContainsKeywordsPredicate
orRecordContainsKeywordsPredicate
in order to extend its behavior.
-
-
Choice Justification: Ultimately, while the current implementation can be considered more troublesome for future developers as they would need to create a new java file for each new parameter, we decided that it was more important for us to maintain proper software engineering principles.
-
The Sort
mechanism is facilitated through the use of Comparators in conjunction with the Unique Lists for both
record and patient depending on the programm’s mode when the command is called.
Upon parsing the arguments, SortCommandParser
then checks which mode TeethHub currently is in. If it’s in record /
goTo mode, it creates a RecordComparator
and returns a SortRecordCommand
. Otherwise, a PatientComparator
is called
instead and returns a SortPatientCommand
instead.
When the comparator is retrieved, SortCommandParser then calls orderChecker
from itself to determine the
isReverse
boolean. Finally, the respective parameters are used to create the neccessary SortCommand
and returns
it to AddressBookParser
and then LogicManager
.
On execution, there are two behaviours can occur depending on whether the SortCommand
is a SortPatientCommand
or
SortRecordCommand
. Should it be an instance of SortRecordCommand
, sortRecordsBook(c,isReverse)
is called from
ModelManager, which in turns called sortRecords(c, isReverse)
from VersionedAddressBook
and finally calls
sortStoredList (c, isReverse)
from UniqueRecordList
. This sorts the records currently stored according to the
parsed comparator and hence changes the order records are displayed to the user.
Should it be a SortPatientCommand
instead, the same logic flow occurs except it that calls the respective patient
methods and classes instead.
-
Current Implementation:
SortPatientCommand
&SortRecordCommand
extends from a superSortCommand
class and share a commonSortCommandParser
. They hence share the same command words and allows the user to simply entersort
to sort either the displayed patients or records.-
Alternative 1: Create separate parsers and commands for sorting patients and records respectively.
PatientSortCommand
andPatientSortParserCommand
could sort patients while in patients mode whileRecordSortCommand
andRecordSortParserCommand
could sort records while in records mode.-
Alternative Pros: A clearer distinction of what mode the program is in would be made to the user. Additionally, it would be easier to change the parser behavior if necessary in the future of either sort command without affecting the other.
-
Alternative Cons: It would no longer be possible to change any shared behavior of both sort commands simply by modifying the SortCommand super class.
-
-
Choice Justification: Ultimately, we decided on having a parent SortCommand with its two SortPatientCommand and SortRecordCommand children share a common SortCommandParser. Aside from the fact that
SortRecordCommand
calls the Record equivalent methods and classes thatSortPatientCommand
does, the behavior of both sort commands are essentially the same. As such, it made more sense for us to go ahead with our current implementation.
-
The Stat
mechanism is facilitated through the use of the inbuilt JavaFx framework in conjunction with the data
stored within each Patient
object.
Upon execution of the stat command, the patient to have statistics generated from is set to the StatWindow
file
which then uses it to create the report. As per the diagram below, StatWindow
then obtains all attributes from the
patient and sets the latest teeth image to itself.
The StatWindow
then creates a Map<String, Integer>
with all the valid procedure values
found in Procedure
as the keys and the values set to 0. StatWindow
subsequently iterates through all the records
linked to the set patient checking for the record’s procedure type, incrementing the value paired the respective
procedure by 1 on a hit. It is from this Map that the records table is generated as well as the bar chart. The pie
chart uses the Map as well while ignoring entries whose values have remained at 0.
It is at this point that all necessary data has been populated within the statistics report and it is displayed to the user.
-
Current Implementation: The selected patient is determined by the Index number of the currently displayed patients within the
PatientListPanel
-
Alternative 1: The selected patient is determined by a given keyword. Upon entering the keyword, the program then searches for the patients that match the keyword. Should multiple patients match the keyword, the command would fail and the user would be prompted to refine their search.
-
Alternative Pros: Combines
patientfind
andstat
together, giving the user greater convenience should they know enough unique details of the patient they wish to stat. -
Alternative Cons: Reduces the ease of use of the stat command. As the amount of patients stored within the program increases, so does the probability that the entered keywords would match multiple patients.
-
-
Choice Justification: Ultimately, we decided to stick with Index based selection as it increases the ease of use for the user when there’s a large number of patient entries. As the Index references the displayed patients, the user can hence perform a
patientfind
operation beforehand and locate the desired patient.
-
Although TeethHub already has a built-in auto-load and auto-save, implementing file management would give the user more flexibility with managing data.
PDF export is also implemented so that the user would have an easier time making sense of the data when offline.
The File Management features are: Open
, Import
, Save
, Export
.
* If the file that is being opened/imported is corrupted, an error message is thrown and no change is made.
* If the user inputs an index range for import/export that does not exist, the current indexes that fall within the range are still imported/exported. This is because we want to make things easier on the user.
ℹ️
|
These following two keywords will be used by various File Management features.
|
Suppose you have a data.json
file with the following contents:
The following image illustrates the difference when you open or import data.json
.
As TeethHub already has a built-in auto-load when starting the program, the implemented Open feature is simple.
The Open feature opens the specified file and overwrites the current TeethHub data with the file data.
The Open feature’s format is: open FILE_PATH
-
AddressBookParser creates OpenCommandParser.
-
The OpenCommandParser uses ParserUtil to parse the user input.
-
If the input is valid, ParserUtil creates a ParsedInOut object and returns it to OpenCommandParser.
-
OpenCommandParser creates OpenCommand initialized with the ParsedInOut object.
-
OpenCommand checks for if the requested file is a ".json" file, if the file exists, if it is a file, or if it can be read.
-
OpenCommand calls the existing readAddressBook().
As TeethHub already has a built-in auto-load when starting the program, the implemented Import feature makes use of it.
The Import feature opens the specified file and adds the file data to the current TeethHub data.
The Import feature’s format is: import FILE_PATH INDEX_RANGE
-
AddressBookParser creates ImportCommandParser.
-
The ImportCommandParser uses ParserUtil to parse the user input.
-
If the input is valid, ParserUtil creates a ParsedInOut object and returns it to ImportCommandParser.
-
ImportCommandParser creates ImportCommand initialized with the ParsedInOut object.
-
ImportCommand checks for if the requested file is a ".json" file, if the file exists, if it is a file, or if it can be read.
-
ImportCommand calls the existing readAddressBook() on a temporary storage.
6a. ImportCommand adds contents from the temporary storage to the current storage based on the input INDEX_RANGE.
6b. ImportCommand adds all contents from the temporary storage to the current storage if INDEX_RANGE isall
.
As TeethHub already has a built-in auto-save when exiting the program, the implemented Save feature makes use of it.
The Save feature saves all current TeethHub data to the specified file.
In addition to that, the Save can also save to PDF, using the Apache PDFBox.
In the Open Command Sequence Diagram above, you can see that OpenCommand creates an InOutAddressBookStorage. The InOutAddressBookStorage has the capability to call the existing saveAddressBook() and also a new saveAsPdf().
The Save feature’s format is: save FILE_PATH
-
AddressBookParser creates SaveCommandParser.
-
The SaveCommandParser uses ParserUtil to parse the user input.
-
If the input is valid, ParserUtil creates a ParsedInOut object and returns it to SaveCommandParser.
-
SaveCommandParser creates SaveCommand initialized with the ParsedInOut object.
-
SaveCommand checks for if the requested file is a ".json" file or ".pdf" file. It also checks if the file is Read-only.
5a. SaveCommand calls the existing saveAddressBook() if the requested file is a ".json" file.
5b. SaveCommand calls the new saveAsPdf() if the requested file is a ".pdf" file.
As TeethHub already has a built-in auto-save when starting the program, the implemented Export feature makes use of it.
The Export feature saves specified patients in the current TeethHub data to the specified file.
The Export feature’s format is: export FILE_PATH INDEX_RANGE
-
AddressBookParser creates ExportCommandParser.
-
The ExportCommandParser uses ParserUtil to parse the user input.
-
If the input is valid, ParserUtil creates a ParsedInOut object and returns it to ExportCommandParser.
-
ExportCommandParser creates ImportCommand initialized with the ParsedInOut object.
-
ExportCommand checks if INDEX_RANGE is
all
.
5a. ExportCommand calls SaveCommand if INDEX_RANGE isall
. Refer to Save feature.
5b. Otherwise, ExportCommand add contents from the current storage to the temporary storage based on the input INDEX_RANGE. -
ExportCommand checks for if the requested file is a ".json" file or ".pdf" file. It also checks if the file is Read-only.
6a. ExportCommand calls the existing saveAddressBook() if the requested file is a ".json" file.
6b. ExportCommand calls the new saveAsPdf() if the requested file is a ".pdf" file.
-
Current implementation: (Open/Import/Save/Export)Command → InOutAddressBookStorage → JsonUtil → FileUtil
-
Alternative 1: (Open/Import/Save/Export)Command → Json Util → FileUtil
-
Alternative Pros: Less overhead and faster runtime as there are less classes to go through.
-
Alternative Cons: InOutAddressBookStorage does some file reading/writing error handling. Bypassing InOutAddressBookStorage would require the same error handling in (Open/Import/Save/Export)Command. Since (Open/Import/Save/Export)Command is not called when the program starts, we cannot move the error handling from InOutAddressBookStorage to (Open/Import/Save/Export)Command. In that case, we would have to copy the error handling instead, which means that we now have a duplicate logic, which is also not ideal.
-
-
Alternative 2: (Open/Import/Save/Export)Command → FileUtil
-
Alternative Pros: Same as Alternative 1.
-
Alternative Cons: Same as Alternative 1. In addition to that: The features of Json Util would need to be re-implemented in (Open/Import/Save/Export)Command, which would also lead to duplicate logic.
-
-
Choice Justification:
Since:
There already is a file reading/writing error handling implemented in InOutAddressBookStorage.
There already is .json handling implemented in JsonUtil.
It would be logical to make use of them instead of re-implementing them.
-
-
Current implementation: (Save/Export)Command → InOutAddressBookStorage → PdfUtil
-
Alternative 1: (Save/Export)Command → PdfUtil
-
Alternative Pros: Less overhead and faster runtime as there are less classes to go through.
-
Alternative Cons: InOutAddressBookStorage does some file reading/writing error handling. Bypassing InOutAddressBookStorage would require the same error handling in (Open/Import/Save/Export)Command. Since (Open/Import/Save/Export)Command is not called when the program starts, we cannot move the error handling from InOutAddressBookStorage to (Open/Import/Save/Export)Command. In that case, we would have to copy the error handling instead, which means that we now have a duplicate logic, which is also not ideal.
-
-
Alternative 2.1: (Save/Export)Command → PdfUtil → FileUtil
-
Alternative 2.2: (Save/Export)Command → FileUtil
-
Alternative Pros: Same as Alternative 1.
-
Alternative Cons: Same as Alternative 1. In addition to that: Passing the job to FileUtil would require implementing Pdf creation and saving that is already present in the third party library Apache PDFBox. Hence the job is passed to PdfUtil and stops there as it calls the already present writing methods of Apache PDFBox.
-
-
Choice Justification:
Since:
There already is a file reading/writing error handling implemented in InOutAddressBookStorage.
There already is .pdf handling implemented in Apache PDFBox.
It would be logical to make use of them instead of re-implementing them.
-
-
Current implementation: The Import/Export features accept index ranges that are larger than the actual index range of the content to be imported/exported. Indexes out of range are simply ignored.
E.g. There are patients from index 1 to index 30. User inputsexport test.json 10-40
. Patients with index 10 to 30 are exported, the requested 31 to 40 is ignored.-
Alternative: Don’t allow indexes out of range for Import/Export.
-
Alternative Pros: User cannot input a very large index range. This prevents a scenario where a very large range causes slow runtime and increased memory due to the amount of indexes to process.
-
Alternative Cons: User may feel frustration of being denied due to minor mistakes. e.g.
export data.json 1-31
being rejected when there are only 30 entries.
-
-
Choice Justification: As our goal when designing TeethHub was to make things easier for the user, we decided to allow the user to make some mistakes.
-
-
Current implementation: The Import/Export features accept the
all
keyword in place of an index range.-
Alternative: Don’t parse "all" keyword for Import/Export.
-
Alternative Pros: Faster runtime as there are less characters in the regex to match.
-
Alternative Cons: User would need to know the total amount of patients in the external file if importing. Otherwise, the user might resort to inputting a very large index range, which would slow down runtime and increase memory needed due to the amount of indexes to process.
-
-
Choice Justification: We chose the current implementation to provide an alternative so that it would discourage users from inputting a very large index range.
-
-
Current implementation: The Export feature calls the Save feature when the
all
keyword is detected.-
Alternative: Don’t parse
all
keyword for Export.-
Alternative Pros: Less overhead and faster runtime as there the regex would not need to look for
all
-
Alternative Cons: In the current implementation, Import and Export share the same parser as Import and Export share the same format of
command FILE_PATH INDEX_RANGE
. Since Import uses theall
keyword, not parsingall
would require an additional parser for Export.
-
-
Choice Justification: We chose the current implementation so as to reduce duplicate logic and improve user experience. As the accepted inputs of Open and Save are the same (except for .pdf), the user may expect the same accepted inputs for Import and Export as well.
-
As TeethHub contains commands that are similar, we decided to implement a Suggestion feature.
This feature was designed to help users who are familiar with older versions of TeethHub or Address Book 4, as they have the names of old commands.
When the user types a Common command, a suggestion will be displayed asking the user if they meant to type something else.
We define a Common command as a command whose name is used by the Patient commands, Record commands and/or Task commands.
For example, as there are patientadd
, recordadd
and taskadd
, the Common command would be add
.
As TeethHub has a Patient Mode and a Record Mode, only commands that can be used in the user’s current mode will be displayed.
-
Current implementation: When the user types a Common command, suggestions are displayed.
-
Alternative: Show Help window if the user inputs invalid commands
N
times in a row.-
Alternative Pros: Only 1 implementation, as opposed to an implementation for each Common command.
-
Alternative Cons: Might be rude.
-
-
Choice Justification: We chose the current implementation as we occasionally found ourselves and other users typing
add
to add something,edit
to edit something and so on. This implementation was designed to tackle this issue.
-
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.9, “Configuration”) -
The
Logger
for a class can be obtained usingLogsCenter.getLogger(Class)
which will log messages according to the specified logging level -
Currently log messages are output through:
Console
and to a.log
file.
Logging Levels
-
SEVERE
: Critical problem detected which may possibly cause the termination of the application -
WARNING
: Can continue, but with caution -
INFO
: Information showing the noteworthy actions by the App -
FINE
: Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size
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. |
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.
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.
Here are the steps to convert the project documentation files to PDF format.
-
Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the
docs/
directory to HTML format. -
Go to your generated HTML files in the
build/docs
folder, right click on them and selectOpen with
→Google Chrome
. -
Within Chrome, click on the
Print
option in Chrome’s menu. -
Set the destination to
Save as PDF
, then clickSave
to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.
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.
|
Attribute name | Description | Default value |
---|---|---|
|
The name of the website. If set, the name will be displayed near the top of the page. |
not set |
|
URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar. |
not set |
|
Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. |
not set |
Each .adoc
file may also specify some file-specific asciidoc attributes which affects how the file is rendered.
Asciidoctor’s built-in attributes may be specified and used as well.
💡
|
Attributes left unset in .adoc files will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
Site section that the document belongs to.
This will cause the associated item in the navigation bar to be highlighted.
One of: * Official SE-EDU projects only |
not set |
|
Set this attribute to remove the site navigation bar. |
not set |
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 |
There are three ways to run tests.
💡
|
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. |
Method 1: Using IntelliJ JUnit test runner
-
To run all tests, right-click on the
src/test/java
folder and chooseRun 'All Tests'
-
To run a subset of tests, you can right-click on a test package, test class, or a test and choose
Run 'ABC'
Method 2: Using Gradle
-
Open a console and run the command
gradlew clean allTests
(Mac/Linux:./gradlew clean allTests
)
ℹ️
|
See UsingGradle.adoc for more info on how to run tests using Gradle. |
Method 3: Using Gradle (headless)
Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.
To run tests in headless mode, open a console and run the command gradlew clean headless allTests
(Mac/Linux: ./gradlew clean headless allTests
)
We have two types of tests:
-
GUI Tests - These are tests involving the GUI. They include,
-
System Tests that test the entire App by simulating user actions on the GUI. These are in the
systemtests
package. -
Unit tests that test the individual components. These are in
seedu.address.ui
package.
-
-
Non-GUI Tests - These are tests not involving the GUI. They include,
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.address.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.address.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g.seedu.address.logic.LogicManagerTest
-
See UsingGradle.adoc to learn how to use Gradle for build automation.
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.
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.
Here are the steps to create a new release.
-
Update the version number in
MainApp.java
. -
Generate a JAR file using Gradle.
-
Tag the repository with the version number. e.g.
v0.1
-
Create a new release using GitHub and upload the JAR file you created.
A project often depends on third-party libraries. For example, Address Book depends on the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
-
Include those libraries in the repository (this bloats the repository size)
-
Require developers to download those libraries manually (this creates extra work for developers)
Suggested path for new programmers:
-
First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in Section A.1, “Improving each component”.
-
Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. Section A.2, “Creating a new command:
remark
” explains how to go about adding such a feature.
Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work).
Scenario: You are in charge of logic
. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases.
💡
|
Do take a look at Section 2.3, “Logic component” before attempting to modify the Logic component.
|
-
Add a shorthand equivalent alias for each of the individual commands. For example, besides typing
clear
, the user can also typec
to remove all persons in the list.-
Hints
-
Just like we store each individual command word constant
COMMAND_WORD
inside*Command.java
(e.g.PatientFindCommand#COMMAND_WORD
,PatientDeleteCommand#COMMAND_WORD
), you need a new constant for aliases as well (e.g.PatientFindCommand#COMMAND_ALIAS
). -
AddressBookParser
is responsible for analyzing command words.
-
-
Solution
-
Modify the switch statement in
AddressBookParser#parseCommand(String)
such that both the proper command word and alias can be used to execute the same intended command. -
Add new tests for each of the aliases that you have added.
-
Update the user guide to document the new aliases.
-
See this PR for the full solution.
-
-
Scenario: You are in charge of model
. One day, the logic
-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command.
💡
|
Do take a look at Section 2.4, “Model component” before attempting to modify the Model component.
|
-
Add a
removeTag(Tag)
method. The specified tag will be removed from everyone in the address book.-
Hints
-
The
Model
and theAddressBook
API need to be updated. -
Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags?
-
Find out which of the existing API methods in
AddressBook
andPerson
classes can be used to implement the tag removal logic.AddressBook
allows you to update a person, andPerson
allows you to update the tags.
-
-
Solution
-
Implement a
removeTag(Tag)
method inAddressBook
. Loop through each person, and remove thetag
from each person. -
Add a new API method
deleteTag(Tag)
inModelManager
. YourModelManager
should callAddressBook#removeTag(Tag)
. -
Add new tests for each of the new public methods that you have added.
-
See this PR for the full solution.
-
-
Scenario: You are in charge of ui
. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn’t prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems.
💡
|
Do take a look at Section 2.2, “UI component” before attempting to modify the UI component.
|
-
Use different colors for different tags inside person cards. For example,
friends
tags can be all in brown, andcolleagues
tags can be all in yellow.Before
After
-
Hints
-
The tag labels are created inside the
PersonCard
constructor (new Label(tag.tagName)
). JavaFX’sLabel
class allows you to modify the style of each Label, such as changing its color. -
Use the .css attribute
-fx-background-color
to add a color. -
You may wish to modify
DarkTheme.css
to include some pre-defined colors using css, especially if you have experience with web-based css.
-
-
Solution
-
You can modify the existing test methods for
PersonCard
's to include testing the tag’s color as well. -
See this PR for the full solution.
-
The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes.
-
-
-
-
Modify
NewResultAvailableEvent
such thatResultDisplay
can show a different style on error (currently it shows the same regardless of errors).Before
After
-
Hints
-
NewResultAvailableEvent
is raised byCommandBox
which also knows whether the result is a success or failure, and is caught byResultDisplay
which is where we want to change the style to. -
Refer to
CommandBox
for an example on how to display an error.
-
-
Solution
-
Modify
NewResultAvailableEvent
's constructor so that users of the event can indicate whether an error has occurred. -
Modify
ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)
to react to this event appropriately. -
You can write two different kinds of tests to ensure that the functionality works:
-
The unit tests for
ResultDisplay
can be modified to include verification of the color. -
The system tests
AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()
to include verification forResultDisplay
as well.
-
-
See this PR for the full solution.
-
Do read the commits one at a time if you feel overwhelmed.
-
-
-
-
Modify the
StatusBarFooter
to show the total number of people in the address book.Before
After
-
Hints
-
StatusBarFooter.fxml
will need a newStatusBar
. Be sure to set theGridPane.columnIndex
properly for eachStatusBar
to avoid misalignment! -
StatusBarFooter
needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated.
-
-
Solution
-
Modify the constructor of
StatusBarFooter
to take in the number of persons when the application just started. -
Use
StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)
to update the number of persons whenever there are new changes to the addressbook. -
For tests, modify
StatusBarFooterHandle
by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -
For system tests, modify
AddressBookSystemTest
to also verify the new total number of persons status bar. -
See this PR for the full solution.
-
-
Scenario: You are in charge of storage
. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage.
💡
|
Do take a look at Section 2.5, “Storage component” before attempting to modify the Storage component.
|
-
Add a new method
backupAddressBook(ReadOnlyAddressBook)
, so that the address book can be saved in a fixed temporary location.-
Hint
-
Add the API method in
AddressBookStorage
interface. -
Implement the logic in
StorageManager
andInOutAddressBookStorage
class.
-
-
Solution
-
See this PR for the full solution.
-
-
By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app.
Scenario: You are a software maintainer for addressbook
, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible remark
field for each contact, rather than relying on tags alone. After designing the specification for the remark
command, you are convinced that this feature is worth implementing. Your job is to implement the remark
command.
Edits the remark for a person specified in the INDEX
.
Format: remark INDEX r/[REMARK]
Examples:
-
remark 1 r/Likes to drink coffee.
Edits the remark for the first person toLikes to drink coffee.
-
remark 1 r/
Removes the remark for the first person.
Let’s start by teaching the application how to parse a remark
command. We will add the logic of remark
later.
Main:
-
Add a
RemarkCommand
that extendsCommand
. Upon execution, it should just throw anException
. -
Modify
AddressBookParser
to accept aRemarkCommand
.
Tests:
-
Add
RemarkCommandTest
that tests thatexecute()
throws an Exception. -
Add new test method to
AddressBookParserTest
, which tests that typing "remark" returns an instance ofRemarkCommand
.
Let’s teach the application to parse arguments that our remark
command will accept. E.g. 1 r/Likes to drink coffee.
Main:
-
Modify
RemarkCommand
to take in anIndex
andString
and print those two parameters as the error message. -
Add
RemarkCommandParser
that knows how to parse two arguments, one index and one with prefix 'r/'. -
Modify
AddressBookParser
to use the newly implementedRemarkCommandParser
.
Tests:
-
Modify
RemarkCommandTest
to test theRemarkCommand#equals()
method. -
Add
RemarkCommandParserTest
that tests different boundary values forRemarkCommandParser
. -
Modify
AddressBookParserTest
to test that the correct command is generated according to the user input.
Let’s add a placeholder on all our PersonCard
s to display a remark for each person later.
Main:
-
Add a
Label
with any random text insidePersonListCard.fxml
. -
Add FXML annotation in
PersonCard
to tie the variable to the actual label.
Tests:
-
Modify
PersonCardHandle
so that future tests can read the contents of the remark label.
We have to properly encapsulate the remark in our Person
class. Instead of just using a String
, let’s follow the conventional class structure that the codebase already uses by adding a Remark
class.
Main:
-
Add
Remark
to model component (you can copy fromAddress
, remove the regex and change the names accordingly). -
Modify
RemarkCommand
to now take in aRemark
instead of aString
.
Tests:
-
Add test for
Remark
, to test theRemark#equals()
method.
Now we have the Remark
class, we need to actually use it inside Person
.
Main:
-
Add
getRemark()
inPerson
. -
You may assume that the user will not be able to use the
add
andedit
commands to modify the remarks field (i.e. the person will be created without a remark). -
Modify
SampleDataUtil
to add remarks for the sample data (delete yourdata/TeethHub.json
so that the application will load the sample data when you launch it.)
We now have Remark
s for Person
s, but they will be gone when we exit the application. Let’s modify JsonAdaptedPerson
to include a Remark
field so that it will be saved.
Main:
-
Add a new JSON field for
Remark
.
Tests:
-
Fix
invalidAndValidPersonAddressBook.json
,typicalPersonsAddressBook.json
,validAddressBook.json
etc., such that the JSON tests will not fail due to a missingremark
field.
Since Person
can now have a Remark
, we should add a helper method to PersonBuilder
, so that users are able to create remarks when building a Person
.
Tests:
-
Add a new method
withRemark()
forPersonBuilder
. This method will create a newRemark
for the person that it is currently building. -
Try and use the method on any sample
Person
inTypicalPersons
.
Our remark label in PersonCard
is still a placeholder. Let’s bring it to life by binding it with the actual remark
field.
Main:
-
Modify
PersonCard
's constructor to bind theRemark
field to thePerson
's remark.
Tests:
-
Modify
GuiTestAssert#assertCardDisplaysPerson(…)
so that it will compare the now-functioning remark label.
We now have everything set up… but we still can’t modify the remarks. Let’s finish it up by adding in actual logic for our remark
command.
Main:
-
Replace the logic in
RemarkCommand#execute()
(that currently just throws anException
), with the actual logic to modify the remarks of a person.
Tests:
-
Update
RemarkCommandTest
to test that theexecute()
logic works.
See this PR for the step-by-step solution.
Target user profile:
-
is a dental practitioner
-
has a need to manage a significant number of patient information, records and tasks
-
prefer desktop apps over other types
-
can type fast
-
prefers typing over mouse input
-
is reasonably comfortable using CLI apps
-
prefers having graphical feedback from the application
-
has an occasional need to transfer chunks of information in and/or out of an app
Value proposition: manages data faster than a typical mouse/GUI drive app while keeping the graphic benefits of GUI
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
user |
add a new patient’s particulars |
know about their situation |
|
user |
edit my patient’s particulars |
their personal information remains updated |
|
user |
store my patient’s teeth |
track their teeth condition |
|
user |
modify my dental patients' teeth condition |
understand and serve my dental patients' |
|
user |
isolate other patients' information |
focus on the current patient in my clinic |
|
user |
view my patients' dental records |
keep track of my patients' teeth health history |
|
user |
add a new dental record |
store diagnosis and treatments of my patients |
|
user |
delete a dental record |
remove records that have become redundant or irrelevant |
|
user |
edit a dental record |
correct existing dental records |
|
user |
clear all dental records of a patient |
protect the privacy of my patients |
|
user |
add a new task |
keep track of what I need to do |
|
user |
delete a task |
remove tasks that I have already completed or no longer need to do |
|
user |
edit a task |
change details of certain tasks that I have already added |
|
user |
link a patient to a task |
differentiate between tasks that are meant for a patient and normal tasks |
|
user |
set a task to complete |
mark a task as done without needing to edit it again and add a record automatically if the task is linked to a patient |
|
user |
add a record automatically upon completing a task |
save time after completing a task that relates a patient by having the record added automatically |
|
user |
view tasks in a calendar |
see all the dates in which I have tasks starting or ending, keeping better track of urgency of tasks and deadlines |
|
user |
see a statistics report on each patient’s dental history |
have an easier time understanding their potential problems |
|
user |
see a warning come up when I’m exiting the program if there exists duplicate entries |
be reminded to edit them before exiting. |
|
user |
see an overall statistics report on my patients |
analyze potential trends |
|
user |
copy a person |
reduce the time needed to create a new person who has similar records to an existing person in the list |
|
user |
copy a task |
reduce the time needed to create a new task who has similar records to an existing task in the list |
|
user |
create MC based on existing record |
reduce the time needed to input existing data to another system. |
|
user |
open an external file |
work with patients and tasks saved in another file |
|
user |
import specific patients from an external file |
add patients and tasks from another file |
|
user |
save to an external file |
have multiple files for cataloguing |
|
user |
export specific patients to an external file |
have multiple files for cataloguing |
|
user |
save/export to PDF |
have a more presentable format for reading |
|
user |
see suggestions when I type common commands |
do not have to keep consulting the Help page |
|
user |
add my own images into records |
keep track and store relevant images |
|
user |
see relevant dentistry tags on my patients' entries |
have an overview of my patients' condition |
|
user with many persons in the address book |
sort patients by desired parameter |
locate a person easily |
|
user with confidential patient information |
log into the application with a password |
prevent unauthorized access to the application when I am not around |
(For all use cases below, the System is the AddressBook
and the Actor is the user
, unless specified otherwise)
MSS
-
User requests to list persons
-
AddressBook shows a list of persons
-
User requests to delete a specific person in the list
-
AddressBook deletes the person
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
3a1. AddressBook shows an error message.
Use case resumes at step 2.
-
MSS
-
User requests to open a .json file
-
TeethHub opens the file and replaces the current contents with the file contents.
Use case ends.
Extensions
-
1a. The specified path file is not a .json file.
-
1a1. TeethHub shows an error message.
Use case resumes at step 0.
-
-
1b. The specified .json file contents to not conform to TeethHub’s reading algorithm.
-
1b1. TeethHub shows an error message.
Use case resumes at step 0.
-
MSS
-
User requests to save a .json file or a .pdf file.
-
TeethHub saves the file, overwriting if the specified file already exists.
Use case ends.
Extensions
-
1a. The specified file path is not a .json file or a .pdf file.
-
1a1. TeethHub shows an error message.
Use case resumes at step 0.
-
-
1b. The specified file path is read only.
-
1b1. TeethHub shows an error message.
Use case resumes at step 0.
-
MSS
-
User requests to import a .json file.
-
TeethHub imports the file, adding the file contents to the current TeethHub contents.
Use case ends.
Extensions
-
1a. The specified file path is not a .json file.
-
1a1. TeethHub shows an error message.
Use case resumes at step 0.
-
-
1b. The specified file path is read only.
-
1b1. TeethHub shows an error message.
Use case resumes at step 0.
-
1c. The given index is invalid.
-
1c1. TeethHub shows an error message.
Use case resumes at step 0.
-
MSS
-
User requests to export a .json file or a .pdf file.
-
TeethHub exports the specified contents to the file, overwriting if the specified file already exists.
Use case ends.
Extensions
-
1a. The specified file path is not a .json file or a .pdf file.
-
1a1. TeethHub shows an error message.
Use case resumes at step 0.
-
-
1b. The specified file path is read only.
-
1b1. TeethHub shows an error message.
Use case resumes at step 0.
-
1c. The given index is invalid.
-
1c1. TeethHub shows an error message.
Use case resumes at step 0.
-
{More to be added}
-
Should work on any mainstream OS as long as it has Java
9
or higher installed. -
Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-
Should be able to provide all features even when working offline.
-
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.
{More to be added}
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. |
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
{ more test cases … }
-
Switching of UI elements when
goto
command is run-
Prerequisites: List all patients using the
list
command. At least one patient should be displayed in the list.-
Test case:
goto 1
(Patient of index 1)
Expected: The patient list GUI is replaced by the record list GUI. Displays dental records of patient specified by index. The window size may not be optimum. Use the command:back
to revert to the patient list. -
Test case:
back
Expected: No GUI elements changed. Error details shown in the status message. Status bar remains the same.
-
-
-
Switching of UI elements when
back
command is run-
Prerequisites: Run the
goto 1
command. GUI displays dental record list.-
Test case:
back
Expected: Shows the GUI with a set of sample patients. An alert box prompts for confirmation. The window size may not be optimum. Use the command:goto 1
to revert to the dental record list. -
Test case:
goto
Expected: No GUI elements changed. Error details shown in the status message. Status bar remains the same.
-
-
-
Deleting a patient while all patients are listed
-
Prerequisites: List all patients using the
list
command. Multiple patients should be displayed in the list.-
Test case:
delete 1
Expected: First patient is deleted from the list. Details of the deleted patient shown in the status message. Timestamp in the status bar is updated. -
Test case:
delete 0
Expected: No patient is deleted. Error details shown in the status message. Status bar remains the same. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size),delete y
(where y is a negative number),delete z
(where z is not an integer)
Expected: Similar to previous.
-
-
-
Deleting a record while all dental records of a patient are listed
-
Prerequisites: List all dental records of a patient using the
goto x
command (x refers to the index of the patient to be tested). Multiple dental records should be displayed in the list.-
Test case:
recorddelete 1
Expected: First dental record is deleted from the list. Details of the deleted dental record is shown in the status message. Timestamp in the status bar is updated. -
Test case:
recorddelete 0
Expected: No record is deleted. Error details shown in the status message. Status bar remains the same. -
Other incorrect recorddelete commands to try:
recorddelete
,record delete
,recorddelete x
(where x is larger than the list size),recorddelete y
(where y is a negative number),recorddelete z
(where z is not an integer)
Expected: Similar to previous.
-
-
-
Adding a new task while all tasks are listed
-
Prerequisites: List all tasks using the
tasklist
command Tasks should all be displayed in the task list. No tasks with the exact same attributes as the test cases are present in the list.-
Test case:
taskadd ti/Test task sd/today st/1200 et/1300 pri/HIGH
Expected: Task with the title "Test task" is added for with the current date as the start date and end date, a start time of 1200, end time of 1300 and a high priority tag displayed in red, should be added at the bottom of the list of all tasks. -
Test case:
taskadd ti/Test task 2 sd/11-05-2019 ed/16-05-2019 ti/Test task 2 st/1200 et/1300
Expected: Task with the title "Test task 2" is added with a start date of 11-05-2019, an end date of 16-05-2019, a start time of 1200, end time of 1300 and a high priority tag displayed in red, should be added at the bottom of the list of all tasks. -
Test case:
taskadd ti/Test task 2 st/1200 et/1300
Expected: No task is added. Error details shown in the status message showing proper command usage. -
Test case:
taskaddti/Test task 2 st/1200 et/1300
Expected: No task is added. Error details shown in the status message stating an unknown command. -
Other incorrect taskadd commands to try:
task add
,taskad d ti/Test task sd/today st/1200 et/1300 pri/HIGH
Expected: Similar to previous.
-
-
-
Editing a new task while all tasks are listed
-
Prerequisites: List all tasks using the
tasklist
command. There are more than 3 tasks present. Tasks should all be displayed in the task list. No tasks with the exact same attributes as the test cases are present in the list.-
Test case:
taskedit 1 ti/New Edited Title
Expected: First task in the list of tasks has its title changed to "New Edited Title". -
Test case:
taskedit 2 ti/New Edited Title 2 sd/today ed/today
Expected: Second task in the list of tasks has its title changed to "New Edited Title 2", its start date and end date changed to the current date. -
Test case:
taskedit 3 ti/New Edited Title 3 sd/today ed/today st/1300 et/1200
Expected: No task is edited. Error details shown in the status message stating that start time cannot be before end time if start date is the same as the end date. -
Test case:
taskedit x ti/New Edited Title
(where x is larger than the number of tasks listed)
Expected: No task is edited. Error details shown in the status message stating that the task index provided is invalid. -
Test case:
task edit 1 ti/New Edited Title 4
Expected: No task is edited. Error details shown in the status message stating an unknown command. -
Other incorrect taskadd commands to try:
tasedit
,taskedit/ti/Test task sd/today st/1200 et/1300 pri/HIGH
Expected: Similar to previous.
-
-
-
Deleting a task while all tasks are listed
-
Prerequisites: List all tasks using the
tasklist
command Tasks should all be displayed in the task list.-
Test case:
taskdelete 1
Expected: First task is deleted from the list. Details of the deleted task are shown in the result display box. -
Test case:
taskdelete 0
Expected: No record is deleted. Error details shown in the status message showing invalid command format. -
Test case:
taskdelete0
Expected: No record is deleted. Error details shown in the status message stating an unknown command. -
Other incorrect recorddelete commands to try:
taskdel
,task delete
Expected: Similar to previous.
-
-
-
Exporting specific patients to a .json or .pdf file.
-
Prerequisites: List all patients using the list command. At least one patient should be displayed in the list.
-
Test case:
export test.json 1
Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 in TeethHub should be present, along with all tasks. -
Test case:
export test ! @ # $ % ^ & ( ) _ + - = { } [ ] ; ' , .json 1
Expected: Same as previous. -
Test case:
export test < > : " | ? *.json 1
Expected: Error message Special characters such as › < : | ? * are not allowed." is shown in message box. No file is created/overwritten. -
Test case:
export test.pdf 1
Expected: A .pdf file named test.pdf is created/overwritten in the "data" folder. When opened in PDF viewer (like Adobe Acrobat Reader DC), only the patient that corresponds to index 1 in TeethHub should be present, along with all tasks. -
Test case:
export test.json 0
orexport test.json x
where x is an index not present in the patient list
Expected: A .json file named test.json is created in the "data" folder. When opened in text viewer (like Notepad), no patient should be present, but all tasks are. -
Test case:
export test.txt 1
Expected: Error message "Input file type is not a .json or .pdf." is shown in message box. No file is created/overwritten. -
Test case:
export testfolder/test.json 1
Expected: A folder named "testfolder" is created in the "data" folder and a .json file named test.json is created/overwritten in the "testfolder" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 in TeethHub should be present, along with all tasks. -
Test case:
export testfolder\test.json 1
Expected: Same as previous. -
Test case:
export \testfolder/test.json 1
Expected: Same as previous. -
Test case:
export testfolder\\\\\\\test.json 1
Expected: Same as previous.
-
-
Prerequisites: List all patients using the list command. At least 3 patients should be displayed in the list.
-
Test case:
export test.json 1,3
Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patients that correspond to index 1 and index 3 in TeethHub should be present, along with all tasks. -
Test case:
export test.json 1-3
Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 to index 3 in TeethHub should be present, along with all tasks.
-
-
Prerequisites: List all patients using the list command. At least 5 patients should be displayed in the list.
-
Test case:
export test.json 1,3,5
Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patients that correspond to index 1, index 3 and index 5 in TeethHub should be present, along with all tasks. -
Test case:
export test.json 1,3-5
Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 and index 3 to index 5 in TeethHub should be present, along with all tasks. -
Test case:
export test.json 1-3-5
Expected: Error message "Invalid index range! Please input a positive unsigned index range." is shown in message box. No file is created/overwritten. -
Test case:
export test.json 1,,5
Expected: Error message "Invalid index range! Please input a positive unsigned index range." is shown in message box. No file is created/overwritten. -
Test case:
export test.json all
Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), all in TeethHub should be present, along with all tasks. Result should be the same as usingsave test.json
.
-
-
-
Trying to open missing/corrupted data files.
-
Prerequisites: Save or Export a .json file with
save test.json
. Open the .json file in the "data" folder with a text editor (like Notepad). Delete the first line and save.-
Test case:
open test.json
Expected: Error message "Data file is not in the correct format." is shown in message box.
-
-
Prerequisites: Make sure there is no file in the "data" folder named "test.json".
-
Test case:
open test.json
Expected: Error message "File not found!" is shown in message box. { more test cases … }
-
-