CATcher:
MarkBind:
RepoSense:
TEAMMATES:
Angular components are split into three parts, *.component.ts
, *.component.html
and *.component.css
*.component.ts
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css']
})
This segment is found at the top of the *.component.ts
files.
selector
indicates the keyword that will be used in *.component.html
files to identify this component. For example, <app-auth> </app-auth>
templateUrl
indicates the filepath to the *.component.html
file.styleUrls
indicates the filepath(s) to the *.component.css
file(s).*.component.html
This is the template file. Template files use mostly HTML syntax, with a bit of angular specific syntax included. This includes the structural directives such as *ngIf, *ngFor, etc. The documentation is quite sufficient for understanding the angular syntax.
*.component.css
This is a stylesheet, using normal css. There is a ::ng-deep
selector available, which promotes a component style to global style.
Arcsecond is a string parsing library for javascript. An example arcsecond parser is as follows:
export const TutorModerationTodoParser = coroutine(function* () {
yield str(TODO_HEADER);
yield whitespace;
const tutorResponses = yield many1(ModerationSectionParser);
const result: TutorModerationTodoParseResult = {
disputesToResolve: tutorResponses
};
return result;
});
str(TODO_HEADER)
matches the starting of the string with TODO_HEADER
.whitespace
matches the next part of the string with one or more whitespaces.many1(ModerationSectionParser)
applies the ModerationSectionParser
one or more times.GraphQL is a architecture for building APIs like REST. Unlike REST where the server defines the structure of the response, in GraphQL, the client and request the exact data they need.
Apple laptops changed to using ARM64 architecture back in 2020. This meant that Node versions released before then were not directly supported by the ARM64 architecture. This caused issues with the github actions. There is a workaround for this by running arch -x86_64
and manually installing node instead of using the setup-node Github action, but the simpler solution was to upgrade the test to use Node version 16.x.
...
Issue faced: CATcher uses node v16.x while WATcher uses node v14.x, it is hard to switch between node versions quickly when working on both projects
Tool used: Used nvm to easily manage and switch between different node versions locally
A typical component in Angular consists of 3 files:
Each component can have a module file where we can state the components/modules that this component is dependent on (i.e. the imports array) and the components that is provided by this module (i.e. the declarations array). This helps increasing the modularity and scalability of the whole application.
As a developer coming from React, here are some clear differences I have observed:
While working on issue #1309, I had to delve deep into how the the IssueTablesComponent is implemented in order to create new tables. A few meaningful observations learnt is summarised as follows:
IssueService
, which is initialized based on IssuesFilter
, and will periodically pull the issues from githubfilters
we inject when creating the IssueTablesComponent
, where the base issues can be filtered down to the issues that we are concerned ofIssueTableComponent
itself, we only specify the action buttons that we want when creating the IssuesTablesComponent
through the actions
input.While working on the new phase (i.e. bug-trimming phase) for CATcher, the team decided to
use a feature-bug-trimming
branch as the target branch we all merge into. However, I noticed that when we created PRs / merged PRs to that feature branch, there are no github workflows/actions being run. As this puts us at the risk of failing tests without knowing, I spent some time to learn how github workflows/actions are being triggered, summarised as follows:
on:
section within the workflow file (i.e. .yml
file)push
or pull-request
to certain branches that are included:on:
# Automatically triggers this workflow when there is a push (i.e. new commit) on any of the included branches
push:
branches: [sample-branch1, sample-branch2]
# Similar to push:, but for PRs towards the included branches
pull_request:
branches: [sample-branch1]
workflow_dispatch
keyword:on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
I learned about the ngx-markdown
library while I was working on a fix to preserve whitespace when converting Markdown to HTML. ngx-markdown
combines multiple different language parsers and renders them in one library. ngx-markdown
supports Marked, Prism.js, Emoji-Toolkit, KaTeX, Mermaid, and Clipboard.js. I learned about configuring the options for the Markdown HTML element.
Marked is the main parser we use for our comment editor in creating/editing issues and responses. I learned that any text that we write in Markdown syntax is converted into HTML elements using Marked. I found out that we can actually override how Marked generates the HTML elements, and we can add more attributes like classes, styles, and even modify the text before rendering it.
WATcher requires node 14 in order to npm install
some of its dependencies. However, instead of having to install and reinstall a different node version between different projects, I can use nvm-windows
to install multiple node versions and switch between them. However, the latest version of nvm-windows
has some issues if youwant to install node 14. After some debugging, I found out that nvm-windows v1.1.11
can install node 14 with no issues.
While working on creating a new phase, I learnt a lot about how phases are managed in CATcher. Every phase has its own phase permissions and phase routing. Phase permissions controls certain tasks. For example, creating a new issue, deleting an issue, editing an issue is only allowed at certain phases. Every phase also has its own routing which is used to load the different pages ranging from, viewing to editing. I also learnt that the repos to hold the issues are generated only at the bug reporting phase.
While I was working on a PR, I was wondering why certain parts of the code are modified after pushing a commit. I then found out that there are commit hooks in place to fix and format and lint issues. Source tree actually allows users to bypass the commit hooks if the changes are irrelevant to the PR that the user is working on.
Angular is the main tool used in both CATcher and WATcher. It is based on TypeScript.
Angular is a component-based framework. Each component is generated with:
Component state is maintained in the .ts file. These state variables can be bound to HTML elements through use of curly braces {{}}.
Angular offers directives such as ngIf, ngFor that allow us to "use" JS in the HTML files.
Services are used for processing, for tasks that don't involve what the user sees. This is different from the .component file, which directly handles things the users see. Services are kept in a separate directory /services/*.
...
CATcher and WATcher are both built using the Angular framework, which is a single-page web appliation framework. Angular comes with a CLI tool to accelerate development.
@Component
decorator in the .ts file identifies the class immediately below it as a component class, and specifies its metadata. It associates a template with the component by referencing the .html file (or with inline code).Drawbacks to using a traditional REST API:
GraphQL API is resolved into its schema and resolvers:
GraphQL allows users to manually choose which fields they want to fetch from the API
VueJS is a JavaScript framework for building user interfaces, similar to React. It offers reactive data binding and a component-based architecture, allowing developers to create reusable components that allow for parent-child relationships. Vue is used extensively in MarkBind to create and render website components, such as pages, boxes, code blocks, etc.
TypeScript is a programming language that builds upon JavaScript by adding static typing, enabling developers to catch errors at compile time and write more maintainable code as compared to JavaScript.
Learned the underlying workings of MarkBind's Highlighter component and how it parses highlighter rules in order to determine the characters or lines to highlight. Learned how to implement an enhancement to the existing feature and add relevant tests and documentation.
List the aspects you learned, and the resources you used to learn them, and a brief summary of each resource.
In order to make more well informed changes and tackle deeper issues, I decided to cover the whole codebase of Markbind just so I could have a much fuller understanding of how different parts worked together.
While doing so, I used a MarkBind site to document the architecture and different packages and classes in the MarkBind codebase. The site can be viewed here: https://gerteck.github.io/mb-architecture/
Collection of Title and headings in generation:
Site/index.ts
.Page.collectHeadingsAndKeywords
records headings and keywords inside rendered page into this.headings and this.keywords respectively.Page Generation and Vue Initialization
core-web/src/index.js
, the setupWithSearch()
updates the SearchData by collecting the pages from the site data.
setupWithSearch()
is added as a script in the file template page.njk
used to render the HTML structure of Markbind pages.VueCommonAppFactory.js
provides a factory function (appFactory) to set up the common data and methods for Vue application shared between server-side and client-side, and provides the common data properties and methods.
searchData[]
and searchCallback()
, which are relevant in the following portion.<searchbar/>
, this is where to use MarkBind's search functionality, we set the appropriate values: <searchbar :data="searchData" :on-hit="searchCallback"></searchbar>
Vue Components: Searchbar/SearchbarPageItem.vue Searchbar.vue
searchData[]
in data
, filters and ranks the data based on keyword matches and populates the dropdown with searchbarPageItems
.on-hit
function (which searchCallback
is passed into) when a search result is selected.searchbar-pageitem
vue component.SearchbarPageItem.vue
About PageFind: A fully static search library that aims to perform well on large sites, while using as little of users bandwidth as possible, and without hosting any infrastructure.
Documentation:
It runs after the website framework, and only requires the folder containing the built static files of the website. A short explanation of how it works would be:
id="pagefind-search-input"
, and initialing a default PageFindUI instance on it, not unlike how algolia search works.https://v3-migration.vuejs.org/migration-build
MarkBind (v5.5.3) is currently using Vue 2. However, Vue 2 has reached EOL and limits the extensibility and maintainability of MarkBind, especially the vue-components package. (UI Library Package).
Vue 2 components can be authored in two different API styles: Option API and Composition API. Read the difference here It was interesting to read the difference between the two.
Server-side rendering: the migration build can be used for SSR, but migrating a custom SSR setup is much more involved. The general idea is replacing vue-server-renderer with @vue/server-renderer. Vue 3 no longer provides a bundle renderer and it is recommended to use Vue 3 SSR with Vite. If you are using Nuxt.js, it is probably better to wait for Nuxt 3.
Currently, MarkBind Vue components are authored in the Options API style. If migrated to Vue 3, we can continue to use this API style.
Vue uses an HTML based template syntax. All Vue templates
<template/>
are syntactically valid HTML tht can be parsed by browsers. Under the hood, Vue compiles the template into highly optimized JS code. Using reactivity, Vue figures out minimal number of components to re-render and apply minimal DOM manipulations.
SFC stands for Single File Components (*.vue files) and is a special file format thaat allows us to encapsulate the template, logic, styling of a Vue component in a single file.
All *.vue
files only consist of three parts, <template>
where HTML content is, <script>
for Vue code and <style>
.
SFC requires a build step, but it allows for pre-compiled templates without runtime compilation cost. SFC is a defining feature of Vue as a framework, and is the reccomended approach of using Vue for Static Site Generation and SPA. Needless to say, MarkBind uses Vue SFCs.
<style>
tags inside SFCs are usually injected as native style tags during development to support hot updates, but for production can be extracted and merged into a single CSS file. (which is what Webpack does)
Reference: https://vuejs.org/guide/extras/rendering-mechanism
Terms:
virtual DOM (VDOM)
- concept where an ideal 'virtual' DOM representation of UI kept in memory, synced with the 'real' DOM. Adopted by React, Vue, other frontend frameworks.mount
: Runtime renderer walk a virtual DOM tree and construct a real DOM tree from it.patch
: Given two copies of virtual DOM trees, renderer walk and compare the two trees, figure out difference, apply changes to actual DOM.The VDOM gives the ability to programmatically create inspect and compose desired UI structures in a declarative way (and leave direct DOM manipulation to renderer).
Render Pipeline What happens when Vue Component is Mounted:
It is possible to render the Vue components into HTML strings on the server, send directly to the browser and finally 'hydrate' static markup into fully interactive app on the client.
Advantages of SSR:
SSR: The server's job is to:
Client-Side Hydration: Once the browser receives the static HTML from the server, the client-side Vue app takes over. Its job is to:
Vue 3 createApp() vs createSSRApp()
createApp
does not bother with hydration. It assumes direct access to the DOM, creates and inserts its rendered HTML. createSSRApp()
used for creating Vue application instance specifically for SSR, where inital HTML is rendered on the server and sent to client for hydration. Instead of rendering (creating and inserting whole HTML from scratch), it does patching. It also does initialization by setting up reactivity, components, global properties etc, event binding during the mount process (aka Hydration).
live-server
– A simple development server with live reloading functionality, used to automatically refresh the browser when changes are made to MarkBind projects.commander.js
– A command-line argument parser for Node.js, used to define and handle CLI commands in MarkBind.fs
(Node.js built-in) – The File System module, used for reading, writing, and managing files and directories in MarkBind projects.lodash
– A utility library providing helper functions for working with arrays, objects, and other JavaScript data structures, improving code efficiency and readability in MarkBindWhile working on Markbind, I thought that it would definitely be essential to survey other Static Site Generators and the competition faced by MarkBind.
Researching other SSGs available (many of which are open source as well) has allowed me to gain a broader picture of the roadmap of MarkBind.
For example, Jekyll is simple and beginner-friendly, often paired with GitHub Pages for easy deployment. It has a large theme ecosystem for rapid site creation. Hugo has exceptional build speeds even for large sites. Other SSGs offer multiple rendering modes (SSG, SSR, CSR) on a per page basis, support react etc. Considering that the community for all these other SSGs are much larger and they have much more resources and manpower to devote, I thought about how MarkBind could learn from these other SSGs.
Overall, some insights that can be applied to MarkBind would be to:
CommonJS (CJS) is the older type of modules and CJS were the only supported style of modules in NodeJS up till v12.
require
and module.exports = {XX:{},}
.cjs
or by using type commonjs
in package.json.EcmaScript Modules (ESM) standardized later and are the only natively supported module style in browsers. It is the (EcmaScript standard) JS standard way of writing modules/
import { XXX } from YYY
(top of file), const { ZZ } = await import("CCC");
and export const XXX = {}
.Issues I faced:
require
) instead of ES module syntax (import
), and hence import
was not working correctly.tsconfig.json
settings appropriately.TypeScript has two main kinds of files. .ts
files are implementation files that contain types and executable code. These are the files that produce .js
outputs, and are where you’d normally write your code. .d.ts files are declaration files that contain only type information. These files don’t produce .js
outputs; they are only used for typechecking.
DefinitelyTyped / @types
: The DefinitelyTyped repository is a centralized repo storing declaration files for thousands of libraries. The vast majority of commonly-used libraries have declaration files available on DefinitelyTyped.
Declaration Maps: .d.ts.map
Declaration map (.d.ts.map) files also known as declaration source maps, contain mapping definitions that link each type declaration generated in .d.ts files back to your original source file (.ts). The mapping definition in these files are in JSON format.
List the aspects you learned, and the resources you used to learn them, and a brief summary of each resource.
A Vue component typically consists of three main sections.
When doing experimental changes, I thought of letting users specify things like font size, font type, etc. Upon looking up the other components and stackoverflow, this is what I found
computed
option. These
properties are automatically updates when the underlying data changes.When writing in Markdown, hyperlinks are created using a specific syntax, but behind the scenes, this Markdown code is converted into HTML.
In Markdown, we use syntax like [Java Docs](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html)
to create a hyperlink. When the Markdown is converted to HTML, it generates an anchor tag in the form of <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/String.html">Java Docs</a>
. This would open the link in the same tab, as no additional attributes are specified.
In contrast, when we write HTML durectly, we can specify additional attributes, such as target="_blank"
, to control how the link behaves. Using the same example, <a href="https://markbind.org/userGuide/templates.html" target="_blank">User Guide: Templates</a>
will ensure that the link opens in a new tab.
CSS (Cascading Style Sheets) is a stylesheet language used to control the presentation of HTML documents.
word-break
property: The word break property provides opportunities for soft wrapping.
slots
API are considered to be owned by the parent component that passes them in and so styles do not apply to them. To apply styles to these components, target the surrounding container and then the style using a CSS selector such as .someClass > *
“virtual” representation of a UI is kept in memory and synced with the “real” DOM
Main benefit of virtual DOM is that it gives the developer the ability to programmatically create, inspect and compose desired UI structures in a declarative way, while leaving the direct DOM manipulation to the renderer
Templates provides easy way to write the virtual dom and get compiled into a render function. However, the virtual dom can directly be created through the render function itself.
The downside of virtual dom is the runtime aspect of it.
the reconciliation algorithm cannot make any assumptions about the incoming virtual DOM tree, so it has to fully traverse the tree and diff the props of every vnode in order to ensure correctness
even if a part of the tree never changes, new vnodes are always created for them on each re-render, resulting in unnecessary memory pressure.
Static hoisting - static codes that are non reactive and never updated are hoisted (removed) from the virtual dom
Patch flags - flags that indicate whether a vnode requires reconciliation. Bitwise checks are used for these flags which are faster
Tree Flattening - Tracked lines of code only applies to those that have patch flags applied
<div> <!-- root block -->
<div>...</div> <!-- not tracked -->
<div :id="id"></div> <!-- tracked -->
<div> <!-- not tracked -->
<div></div> <!-- tracked -->
</div>
</div>
div (block root)
- div with :id binding
- div with binding
Vue component test utilities library: Wrapper
According to my current understanding:
$nextTick()
function of the vm of the wrapper is then called which waits for the next DOM update flush.Markbind utilises several workflow files:
pr-message-reminder.yml
- Extracts out the PR description and checks if a proposed commit message is included.Github Actions is used when writing workflows.
github
context is freuqently used for retrieving useful information of the current workflow run. Some examples used(but not limited to) include :
github.actor
is used to detect the username of the user that triggered the workflow event. It can also be used to detect bots who trigger the events.github.event_name
is used to detect the name of the event that triggered the workflow. In the context of markbind, this is often used to check if the triggered workflow is of a particular event (such as pull request) before running the script.A potential limitation arises when using github.actor
to detect bot accounts. That is, if the bot is a github account that is automated by a user. In this case, github currently has no way to detect such accounts.
Local testing of sites often uses localhost to run up a local server. This often resolves to the IP address of 127.0.0.1.
Markbind allows users to specify the address of localhosts in the IPV4 format. It does not support specifying IPV6 IP addresses.
Markbind uses the all-contributor bot to add contributors to automate the process of adding contributors to the project
{% for %}
and {{ variables }}
, is evaluated and replaced with the corresponding content before moving to the next stage....
TODO: Update
TODO: Update
References:
Familiarised myself with how GitHub Actions work at a high level, and understood basic workflow syntax to debug failing workflow runs.
Issue was discovered to be due to the differences between pull_request
and pull_request_target
. pull_request_target
runs in the context of the base of the pull request, rather than in the context of the merge commit. This causes changes to the workflow in the PR to not be reflected in the runs.
Since the failure was a special case due to the deprecation of certain actions, exception was made to merge with the failing run. Precaution was taken to ensure the change is as intended, but trying it out on personal fork.
References:
The Gradle build typically include three phases: initialization, configuration and execution.
There are four fundamental components in Gradle: Projects, build scripts, tasks and plugin.
A project typically corresponds to a software component that needs to be built, like a library or an application. It might represent a library JAR, a web application, or a distribution ZIP assembled from the JARs produced by other projects. There is a one-to-one relationship between projects and build scripts.
The build script configures the project based on certain rules. It can add plugins to the build process, load dependencies and set up and configure tasks, i.e. individual unit of work that the build process will perform. Plugins can introduce new tasks, object and conventions to abstract duplicating configuration block, increasing the modularity and reusability fo the buld script.
Resources:
CI/CD platform automates build, test and deployment pipeline. There are several main components for Github Actions: workflow, event, job, action and runner
Workflow
configurable automated process that will run one or more jobs. Defined by YAML file in .github/workflows
. A repo can have multiple workflows.
Events
a specific activity that triggers the workflow run, e.g. creating PR and openning issues.
Jobs
A job is a set of steps in the workflow that is executed on the same runner. Each step can be a shell script
or action
Actions
Reusable set of repeated task. This helps reduce the amount of repetative code.
Runners a server that run the workflows when they are triggered. They can be configured with different OS.
apt
package as a job for the Cypress Frontend test and it works, but the former solution is more elegant and concise. Resource referred from GitHub Docs.Learnt about how ESLint ensures a unified style of JS/TS code. Had the chance to go through the ESLint documentation for member-delimiter-style, https://eslint.style/rules/ts/member-delimiter-style, understand how it works, and make the modifications in the ESLint configurations and the codebase to ensure CI job for lintFrontend passes.
Learnt about how Vite build identifies the base directory when serving static assets.
Learnt how to configure Vercel on a GitHub repository.
Learnt about the various aspects to consider when designing and immutable class in Java, such as:
While doing my user experiments on RepoSense, I noticed that the GitHub IDs of contributors were not displayed correctly in the generated contribution dashboards with only the "--repos" flag without the config files. This led me to investigate how RepoSense handles GitHub-specific information and how it differs from Git. Since Git logs only contain commit metadata such as author names and emails, RepoSense is unable to capture GitHub-specific information like GitHub IDs. This is because Git and GitHub, while related, are fundamentally different: Git is a version control system that tracks code changes locally, whereas GitHub is a platform built on top of Git that provides additional features like user profiles and collaboration tools. As a result, the current implementation of RepoSense cannot directly link contributions to GitHub profiles without the config files.
While researching an issue about <hr>
elements in the Markdown files not appearing in the Reposense report, I discovered about the functionality of normalize.css, which provides default styling for this element along with many others. This CSS normalization ensures consistent rendering across different browsers by correcting bugs and browser inconsistencies for more predictable website styling.
Stubbing Methods with when(...).thenReturn(...)
:
I learned that this technique lets me define fixed return values for specific method calls. I can instruct Mockito to return a predetermined value when a certain method is invoked with given arguments.
By stubbing methods with thenReturn()
, I isolate the class under test from its real dependencies. For example, if my code calls:
Course course = mockLogic.getCourse(course.getId());
I can specify:
when(mockLogic.getCourse(course.getId())).thenReturn(expectedCourse);
This approach ensures that the tests only focus on the behavior of the class under test without relying on actual implementations or external systems like databases or service layers.
Simulating State Changes Using doAnswer(...)
:
One of the most powerful techniques I learned was using doAnswer()
to simulate side effects and state changes. This method enables me to dynamically alter the behavior of mocked methods based on actions performed within the test.
Syntax:
doAnswer(invocation -> {
// Custom logic to simulate a side effect or state change
// ...
}).when(mockLogic).someMethod(...);
This technique is especially helpful when my method under test changes the state of its dependencies. For example, when simulating the deletion of an instructor, I can use doAnswer()
so that subsequent calls (such as fetching the instructor by email) return null
—mirroring the real-life behavior after deletion.
Advanced Stubbing Techniques with thenAnswer()
:
In addition to doAnswer()
, I learned how to use thenAnswer()
to provide dynamic responses based on the input parameters of the method call. This custom Answer implementation allows for:
Syntax:
when(mockLogic.someMethod(...)).thenAnswer(invocation -> {
// Custom logic to compute and return a value based on the invocation
// ...
});
This method is ideal when I need the stub to return a value that depends on the input. It adds flexibility to my tests, especially when I want my mocked method to behave differently based on its argument.
Mocks vs. Spies:
I learned that the key difference is:
null
, 0
, or false
) unless explicitly stubbed.Examples:
Mocks:
List<String> mockedList = mock(ArrayList.class);
mockedList.add("item");
verify(mockedList).add("item");
assertEquals(0, mockedList.size()); // Returns default value 0 because it’s fully stubbed.
Spies:
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
spyList.add("item");
verify(spyList).add("item");
assertEquals(1, spyList.size()); // Now size() returns 1 because the real method is called.
When to Use Each:
Advanced Verification Techniques:
Mockito’s advanced verification APIs allow me to check that the correct interactions occur between my class under test and its dependencies—not just that methods were called, but also that they were called in the right order and the correct number of times.
InOrder inOrder = inOrder(mockLogic);
inOrder.verify(mockLogic).startTransaction();
inOrder.verify(mockLogic).executeQuery(anyString());
inOrder.verify(mockLogic).commitTransaction();
times()
, atLeast()
, atMost()
, and never()
to assert the precise number of method invocations.verify(mockLogic, times(2)).processData(any());
verify(mockLogic, never()).handleError(any());
These techniques are crucial when the order and frequency of interactions are essential for the correctness of the code, ensuring that the tested methods not only produce the right results but also follow the intended flow.
I learned these Mockito techniques mainly during the migration of our tests from our previous datastore to Google Cloud PostgreSQL.
The new test classes required a robust mocking framework, so I leveraged a combination of fixed-value stubbing with when(...).thenReturn(...)
, dynamic behavior simulation with doAnswer()
and thenAnswer()
, and careful selection between mocks and spies.
This approach enabled me to write unit tests that are both targeted and reliable.
Although I did not extensively use advanced verification techniques during the migration, I appreciate the potential they offer for validating interactions between components.
These insights have been essential for developing robust tests, and I look forward to applying them in future projects.
...
Angular Component Communication:
@Output
and EventEmitter
.Conditional Class Application:
ngClass
directive.[class]
binding syntax.Event Binding:
(event)
binding syntax to handle user interactions.(change)="handleChange($event)"
to trigger functions when events like change
occur, passing the event object as an argument.Angular Official Documentation:
@Output
and EventEmitter
to enable child-to-parent communication.Udemy Course: "Angular - The Complete Guide" by Maximilian Schwarzmüller:
By combining these resources, I was able to implement a basic dark mode feature that functions effectively but still requires refinement. One key area for improvement is ensuring the dark mode state persists when navigating between routes. Currently, when the route changes (e.g., from localhost:4200/web/
to another route), the boolean variable controlling the dynamic CSS class allocation using ngClass
resets to its default light mode, even if dark mode was active prior to the route change.
I suspect this behavior occurs because the page component is re-rendered during navigation, causing the component's state (including the boolean variable) to be re-initialized. To address this, I plan to research and implement a solution to persist the dark mode state. A promising approach might involve using a shared Angular service to store and manage the state globally, ensuring it remains consistent across routes. While I am not yet an expert in Angular, I am confident that further exploration and practice will help me refine this feature.
Argument Matchers and Primitive vs. Boxed Types
One thing that really stood out to me while working with Mockito was how it handles primitive vs. boxed types. I always assumed that since Boolean
is just the boxed version of boolean
, their argument matchers would behave the same way. However, I discovered that:
anyBoolean()
works for both boolean
and Boolean
, but any(Boolean.class)
only works for Boolean
.Handling Null Values in Argument Matchers
Another unexpected challenge was that any()
does not match null
values. I initially thought any()
would work universally, but my tests kept failing when null
was passed in. After some research, I found that I needed to use nullable(UUID.class)
instead. This was an important learning moment because it made me more aware of how Mockito’s matchers handle null
values differently.
Verifying Method Calls
I also gained a deeper understanding of method verification in Mockito.
verify(mockObject, times(n)).methodToBeTested();
times(1)
ensures the method was called exactly once, while never()
, atLeastOnce()
, and atMost(n)
give more flexibility in defining expected call frequency.Difference Between mock()
and spy()
I decided to dive deeper into stubbing with mockito which led me to learn more about the difference between mock()
and spy()
.
mock(Class.class)
: Creates a mock object that does not execute real method logic.spy(object)
: Creates a partial mock where real methods are called unless stubbed.Mockito Official Documentation:
Mockito Series written by baeldung:
Working with Mockito has made me more confident in writing unit tests. I also gained a much deeper appreciation for argument matchers and null handling. Learning that any()
does not match null but nullable(Class.class)
does was an unexpected but valuable insight. These small details can make or break test reliability, so I’m glad I encountered them early on.
Looking ahead, I aim to sharpen my Mockito skills by exploring advanced features like mocking final classes and static methods. I also plan to experiment further with ArgumentCaptor
, as it offers a more structured approach to inspecting method arguments in tests.
Mockito has already helped me write more effective and maintainable unit tests, and I’m excited to continue improving my testing skills with its advanced features!
List the aspects you learned, and the resources you used to learn them, and a brief summary of each resource.
Coming from a React background, it was interesting to understand how Angular components work and how it talks to each other. A lot of the features are built in with their custom names like ngFor
and (click)
as compared to using JSX. It was very modular in nature which made the learning easier as I can focus on one component without having to break the rest or needing to learn the codebase of more than the surrounding components.
Angular uses a lot more of observables, emittors and listeners which is based on services to communicate between components. It was very different from React Redux and parent-child that I know of. This was what I had to make use of for one of my first PRs #13203 to deal with dynamic child components listening to a hide/show all button.
Angular Crash Course by Traversy Media: A crash course for learning how Angular works for developers with some frontend experience. It covers the basics of Angular, including components, services, and routing.
The use of when()
was rather cool for me coming from JUnit and CS2103T. I did not expect to be able to mock functions and their return values. when()
overrides a function call when that provided function is called, and returns the values given with chain functions. It allows me to perform unit tests much more easily as we do not need to worry about the implementation of the method being complete.
Mockito Documentation: Official documentation for Mockito
This was my first time using Docker and it made development much easier by containing our backend in its own sandbox environment. It keeps the application standardised by running on one type of environment and ensures smooth development by not worrying about multiple types of environment to cater and develop for during production. ...
mock()
, creating a test stub for Logic
.when()
that allows you to specify a return object
using thenReturn()
without running the actual method. This can reduce the chances of any bugs from dependencies affecting the unit test.verify()
that allows you to verify if a certain method call has been made. I think this helps greatly in debugging especially in a large code base.when()
requires the arguments of the method to mock to be specified, in some cases, we cannot pass in the arguments directly due to equality checks for the different objects, hence we can bypass that by using any(<ClassName>.class)
where any argument of that class will trigger the mock method call.when()
does not call the actual method itself.