diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..590e4c730 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,16 @@ +name: MarkBind Deploy + +on: [push] + +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build & Deploy to GitHub Pages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }} + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + BASE_URL: /2024 + uses: MarkBind/markbind-action@1.0.0 diff --git a/README.html b/README.html new file mode 100644 index 000000000..620964b79 --- /dev/null +++ b/README.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + +

CS3281&2 student data website

+ + + diff --git a/README.page-vue-render.js b/README.page-vue-render.js new file mode 100644 index 000000000..24d435f4b --- /dev/null +++ b/README.page-vue-render.js @@ -0,0 +1,13 @@ + + var pageVueRenderFn = function anonymous( +) { +with(this){return _c('div',{attrs:{"id":"app"}},[_c('header',{attrs:{"fixed":""}},[_c('navbar',{attrs:{"placement":"top","type":"primary"},scopedSlots:_u([{key:"brand",fn:function(){return [_c('a',{staticClass:"navbar-brand",attrs:{"href":"/2024/index.html","title":"Home"}},[_v("CS3281&2-2024/Students")])]},proxy:true},{key:"right",fn:function(){return [_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"https://github.com/nus-cs3281/2024"}},[_c('span',[_c('span',{staticClass:"fab fa-github",attrs:{"aria-hidden":"true"}})])])])]},proxy:true}])},[_v(" "),_c('dropdown',{staticClass:"nav-link",scopedSlots:_u([{key:"header",fn:function(){return [_v("CS3281")]},proxy:true}])},[_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/index.html"}},[_v("Students")])]),_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/students/knowledge.html"}},[_v("Knowledge")])]),_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"https://nus-cs3281.github.io/2024-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByAuthors&breakdown=false"}},[_v("Code Dashboard")])])]),_v(" "),_c('dropdown',{staticClass:"nav-link",scopedSlots:_u([{key:"header",fn:function(){return [_v("CS3282")]},proxy:true}])},[_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/cs3282-index.html"}},[_v("Students")])]),_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/students/talksSchedule.html"}},[_v("Lightning Talks")])])]),_v(" "),_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"/2024/instructions.html"}},[_v("Instructions")])]),_v(" "),_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"https://nus-cs3281.github.io/website/"}},[_v("CS3281&2 Website "),_c('span',[_c('span',{staticClass:"glyphicon glyphicon-share-alt",attrs:{"aria-hidden":"true"}})])])])],1)],1),_v(" "),_c('div',{attrs:{"id":"flex-body"}},[_m(0),_v(" "),_c('overlay-source',{staticClass:"fixed-header-padding",attrs:{"id":"page-nav","tag-name":"nav","to":"page-nav"}},[_c('div',{staticClass:"nav-component slim-scroll"})])],1),_v(" "),_m(1)])} +}; + var pageVueStaticRenderFns = [function anonymous( +) { +with(this){return _c('div',{staticClass:"fixed-header-padding",attrs:{"id":"content-wrapper"}},[_c('h1',{attrs:{"id":"cs3281-and-amp-2-student-data-website"}},[_v("CS3281&2 student data website"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#cs3281-and-amp-2-student-data-website","onclick":"event.stopPropagation()"}})])])} +},function anonymous( +) { +with(this){return _c('footer',[_c('div',{staticClass:"text-center"},[_c('p',[_v("["),_c('strong',[_v("This site was generated using "),_c('img',{attrs:{"src":"https://markbind.org/favicon.ico","width":"25"}}),_v(" "),_c('a',{attrs:{"href":"https://markbind.org/"}},[_v("MarkBind 5.2.0")])]),_v(" on Sat, Jan 13, 2024, 7:34:26 AM UTC]"),_c('br'),_v(" "),_c('span',{staticClass:"dimmed"},[_c('small',[_c('small',[_v("favicon.ico of this site was made by "),_c('a',{attrs:{"href":"https://www.flaticon.com/authors/smashicons","title":"Smashicons"}},[_v("Smashicons")]),_v(" from "),_c('a',{attrs:{"href":"https://www.flaticon.com/","title":"Flaticon"}},[_v("www.flaticon.com")]),_v(" is licensed by "),_c('a',{attrs:{"href":"http://creativecommons.org/licenses/by/3.0/","title":"Creative Commons BY 3.0","target":"_blank"}},[_v("CC 3.0 BY")])])])])])])])} +}]; + \ No newline at end of file diff --git a/cs3282-index.html b/cs3282-index.html new file mode 100644 index 000000000..24d28ed19 --- /dev/null +++ b/cs3282-index.html @@ -0,0 +1,23 @@ + + + + + + + CS3282 - 2024 Batch + + + + + + + + +

CS3282 - 2024 Batch

CATcher:

MarkBind:

RepoSense:

TEAMMATES:

CATcher

WONG CHEE HONG

GOH YEE CHONG, GABRIEL

LEE XIONG JIE, ISAAC

VIGNESH SANKAR IYER

MarkBind

LEE WEI, DAVID

ELTON GOH JUN HAO

CHAN YU CHENG

HANNAH CHIA KAI XIN

RepoSense

MARCUS TANG XIN KYE

GOKUL RAJIV

CHARISMA KAUSAR

DAVID GARETH ONG

TEAMMATES

JAY ALJELO SAEZ TING

MOK KHENG SHENG FERGUS

ONG JUN HENG, CEDRIC

NEO WEI QING

KEVIN FOONG WEI TONG

CHANG WENG YEW, NICOLAS

DOMINIC LIM KAI JUN

ZHANG ZIQING

SIM SING YEE, EUNICE

+ + + diff --git a/cs3282-index.page-vue-render.js b/cs3282-index.page-vue-render.js new file mode 100644 index 000000000..a38f152d7 --- /dev/null +++ b/cs3282-index.page-vue-render.js @@ -0,0 +1,49 @@ + + var pageVueRenderFn = function anonymous( +) { +with(this){return _c('div',{attrs:{"id":"app"}},[_c('header',{attrs:{"fixed":""}},[_c('navbar',{attrs:{"placement":"top","type":"primary"},scopedSlots:_u([{key:"brand",fn:function(){return [_c('a',{staticClass:"navbar-brand",attrs:{"href":"/2024/index.html","title":"Home"}},[_v("CS3281&2-2024/Students")])]},proxy:true},{key:"right",fn:function(){return [_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"https://github.com/nus-cs3281/2024"}},[_c('span',[_c('span',{staticClass:"fab fa-github",attrs:{"aria-hidden":"true"}})])])])]},proxy:true}])},[_v(" "),_c('dropdown',{staticClass:"nav-link",scopedSlots:_u([{key:"header",fn:function(){return [_v("CS3281")]},proxy:true}])},[_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/index.html"}},[_v("Students")])]),_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/students/knowledge.html"}},[_v("Knowledge")])]),_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"https://nus-cs3281.github.io/2024-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByAuthors&breakdown=false"}},[_v("Code Dashboard")])])]),_v(" "),_c('dropdown',{staticClass:"nav-link",scopedSlots:_u([{key:"header",fn:function(){return [_v("CS3282")]},proxy:true}])},[_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/cs3282-index.html"}},[_v("Students")])]),_v(" "),_c('li',[_c('a',{staticClass:"dropdown-item",attrs:{"href":"/2024/students/talksSchedule.html"}},[_v("Lightning Talks")])])]),_v(" "),_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"/2024/instructions.html"}},[_v("Instructions")])]),_v(" "),_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"https://nus-cs3281.github.io/website/"}},[_v("CS3281&2 Website "),_c('span',[_c('span',{staticClass:"glyphicon glyphicon-share-alt",attrs:{"aria-hidden":"true"}})])])])],1)],1),_v(" "),_c('div',{attrs:{"id":"flex-body"}},[_c('div',{staticClass:"fixed-header-padding",attrs:{"id":"content-wrapper"}},[_m(0),_v(" "),_m(1),_v(" "),_m(2),_v(" "),_m(3),_v(" "),_m(4),_v(" "),_m(5),_v(" "),_m(6),_v(" "),_m(7),_v(" "),_m(8),_v(" "),_m(9),_v(" "),_c('div',[_c('box',[_c('h2',{attrs:{"id":"wong-chee-hong"}},[_v("WONG CHEE HONG"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#wong-chee-hong","onclick":"event.stopPropagation()"}})]),_v(" "),_c('div',{staticClass:"container"},[_c('div',{staticClass:"row"},[_c('div',{staticClass:"col"},[_c('img',{attrs:{"src":"/2024/students/cheehongw/photo.png","width":"100"}}),_c('br')]),_v(" "),_c('div',{staticClass:"col"},[_c('p',[_c('strong',[_v("GitHub:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://www.github.com/cheehongw"}},[_v("https://www.github.com/cheehongw")])]),_c('br')]),_v(" "),_c('p',[_c('strong',[_v("Projects:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://github.com/CATcher-org/CATcher"}},[_v("CATcher")])])])])])]),_v(" "),_c('p'),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Progress")])])]},proxy:true}])},[_v(" "),_c('div')]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Knowledge gained")])])]},proxy:true}])},[_v(" "),_c('div',[_c('h2',{attrs:{"id":"angular-essentials"}},[_v("Angular Essentials"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#angular-essentials","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("I had contributed to CATcher as part of IWM, but I have never really approached the Angular aspects of the project.")]),_v(" "),_c('p',[_v("Essentially, the core ideas behind Angular involves:")]),_v(" "),_c('ul',[_c('li',[_v("Components, a TypeScript class with "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("@Component")]),_v(" decorator, an HTML template and styles.\n"),_c('ul',[_c('li',[_v("The decorator accepts parameters that help Angular know which HTML file is the component's template and which css file is the component's styles.")]),_v(" "),_c('li',[_v("The decorator also accepts a parameter that is the component's selector, which is how we can reuse this component as an HTML element in other HTML files.")])])]),_v(" "),_c('li',[_v("An HTML template that instructs Angular how to render the component")]),_v(" "),_c('li',[_v("An optional set of CSS styles that define the appearance of the template's HTML elements")])]),_v(" "),_c('p',[_v("The other key concepts include event bindings and property binding that link the template to the TypeScript class. Knowing these essentials allowed me to fix "),_c('a',{attrs:{"href":"https://github.com/CATcher-org/WATcher/pull/57"}},[_v("WATcher PR#57")]),_v(".")]),_v(" "),_c('p',[_v("Another key part of Angular is its Dependency Injection system and services. Angular allows us to provide dependencies at different levels of the application, and how the dependencies are instantiated.")]),_v(" "),_c('ul',[_c('li',[_v("For example, when you providing a service at the root level, Angular creates a single, shared instance of the service and injects it into any class that asks for it.")]),_v(" "),_c('li',[_v("Also, it seems like most of WATcher and CATcher's services are provided at the root level.")])]),_v(" "),_c('p',[_v("Finally, as part of fixing \""),_c('a',{attrs:{"href":"https://github.com/CATcher-org/WATcher/pull/92"}},[_v("Remove label-filter-bar as module export #92")]),_v("\", I also learned about how related components are organized and grouped into modules. Each Module are self-contained and provide a certain set of functionality and components related to that module, thereby achieving separation of concerns.")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/what-is-angular"}},[_v("https://angular.io/guide/what-is-angular")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/dependency-injection"}},[_v("https://angular.io/guide/dependency-injection")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/ngmodules"}},[_v("https://angular.io/guide/ngmodules")])])]),_v(" "),_c('h2',{attrs:{"id":"e2e-testing-with-playwright"}},[_v("E2E Testing with Playwright"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#e2e-testing-with-playwright","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("After having 2 separate hotfixes pushed in a single semester, I started to look more deeply into ensuring the robustness of our application. During these 2 hotfixes, bugs were only uncovered during manual testing. However, it is time consuming to conduct manual tests, and we need to find a way to automate it. E2E tests simulate user interactions such as clicks and typing and is a useful way to ensure our end-product is performing as expected.")]),_v(" "),_c('p',[_v("During this semester, one of the high priority issues was to migrate our E2E solution away from Protractor. As such, I have investigated Cypress and Playwright as two potential E2E solutions.")]),_v(" "),_c('h4',{attrs:{"id":"mocking-services"}},[_v("Mocking services"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#mocking-services","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("When performing migration from Protractor to Playwright, I learned about the different strategies E2E tests can be conducted. Typically, we would want to conduct E2E tests against our production server, since that is what our end users will be using. However, since CATcher depends alot on GitHub's API for its functionality, we are unable to perform automated tests against GitHub. A second strategy would be to mock the functions that hit GitHub's API, and we would test solely the functionalities and behaviours of the app. This let me realized that there is a test vs production version of CATcher.")]),_v(" "),_c('p',[_v("I have also looked into whether it is possible to perform E2E testing against the production server, since one of the bugs fixed in the hotfixes can only be caught if we did not adopt a mocking strategy. One of the key feasibility concerns I had with testing against the GitHub API was simulating user authentication. This was because authenticating with GitHub requires multi-factor authentication, something that is difficult to achieve with automated E2E testing. Some potential solutions to bypassing MFA would be to use TOTP, which can be generated programmatically. More research will be needed in this area.")]),_v(" "),_c('h4',{attrs:{"id":"aspects-learnt"}},[_v("Aspects Learnt"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#aspects-learnt","onclick":"event.stopPropagation()"}})]),_v(" "),_c('ul',[_c('li',[_v("Configuring and setting up Playwright for a project.")]),_v(" "),_c('li',[_v("Learned about how Playwright/Cypress/Protractor identifies and interacts with HTML elements using selectors.")]),_v(" "),_c('li',[_v("Learned about how CATcher API calls are mocked during E2E testing")])]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://playwright.dev"}},[_v("https://playwright.dev")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://www.cypress.io/"}},[_v("https://www.cypress.io/")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://github.com/CATcher-org/CATcher/pull/539"}},[_v("This pull request by ptvrajsk documenting how he implemented E2E with Protractor")])])]),_v(" "),_c('h2',{attrs:{"id":"github-actions"}},[_v("Github Actions"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#github-actions","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("I also picked up Github Actions when contributing to the CI/CD pipeline in "),_c('a',{attrs:{"href":"https://github.com/CATcher-org/WATcher/pull/81"}},[_v("Enable linting in Github workflow #81")]),_v(". I learned how Github Actions are set up and how they can be triggered upon pushing to main/master and also on pull requests.")]),_v(" "),_c('p',[_v("Furthermore, I learnt how we can use matrix strategies to run the same job with different parameters, such as different OS and different node versions.")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://docs.github.com/en/actions/quickstart"}},[_v("https://docs.github.com/en/actions/quickstart")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs"}},[_v("https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs")])])]),_v(" "),_c('h2',{attrs:{"id":"rxjs-and-the-observer-pattern"}},[_v("RxJS and the Observer Pattern"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#rxjs-and-the-observer-pattern","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Part of working with CATcher source code was frequently encountering Observables and Observers. RxJS supports "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("Observers")]),_v(" and "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("Observables")]),_v(", allowing updates to some "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("Observable")]),_v(" to be received by some "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("Observer")]),_v(" that is subscribed to it. With this pattern, we can trigger updates in many dependent objects automatically and asynchronously when some object state changes.")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://rxjs.dev/"}},[_v("https://rxjs.dev/")])])])])]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Observations")]),_v(" from external projects")])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"project-foo"}},[_v("Project: Foo"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#project-foo","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give an intro to the project here ...")]),_v(" "),_c('h3',{attrs:{"id":"my-contributions"}},[_v("My Contributions"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-contributions","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give a description of your contributions, including links to relevant PRs")]),_v(" "),_c('h3',{attrs:{"id":"my-learning-record"}},[_v("My Learning Record"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-learning-record","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give tools/technologies you learned here. Include resources you used, and a brief summary of the resource.")])])])],1)],1),_v(" "),_c('div',[_c('box',[_c('h2',{attrs:{"id":"goh-yee-chong-gabriel"}},[_v("GOH YEE CHONG, GABRIEL"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#goh-yee-chong-gabriel","onclick":"event.stopPropagation()"}})]),_v(" "),_c('div',{staticClass:"container"},[_c('div',{staticClass:"row"},[_c('div',{staticClass:"col"},[_c('img',{attrs:{"src":"/2024/students/gycgabriel/photo.png","width":"100"}}),_c('br')]),_v(" "),_c('div',{staticClass:"col"},[_c('p',[_c('strong',[_v("GitHub:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://www.github.com/gycgabriel"}},[_v("https://www.github.com/gycgabriel")])]),_c('br')]),_v(" "),_c('p',[_c('strong',[_v("Projects:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://github.com/CATcher-org/CATcher"}},[_v("CATcher")]),_v(", "),_c('a',{attrs:{"href":""}})])])])])]),_v(" "),_c('p'),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Progress")])])]},proxy:true}])},[_v(" "),_c('div')]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Knowledge gained")])])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"error-messages-and-hint-labels-in-angular-forms"}},[_v("Error messages and Hint labels in Angular forms"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#error-messages-and-hint-labels-in-angular-forms","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Forms have a "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("FormGroup")]),_v(", where each part of the form is controled by a "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("FormControl")]),_v(" component.")]),_v(" "),_c('p',[_v("In the ts file, Validators check and ensure the form is valid for submission. If it is not, the submit button is greyed out.")]),_v(" "),_c('p',[_v("In the html file, the individual form input boxes are built, and shown with "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("*ngIf")]),_v(" statements. "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("")]),_v(" also has additional parameters to specify whether the input is required and the max length of the input. The form error messages are programmed here in the html file, for example:")]),_v(" "),_c('pre',[_c('code',{pre:true,attrs:{"class":"hljs"}},[_c('span',[_v("\n")]),_c('span',[_v(" Title required.\n")]),_c('span',[_v("\n")])])]),_c('p',[_v("Hint labels can be used to show the remaining characters in a input box with limited characters when approaching that limit.")]),_v(" "),_c('p',[_v("While a string with validators could be used to instantiate a "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("FormGroup")]),_v(", a "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("FormControl")]),_v(" element ensured that validators are processed such that the form error messages are shown in components that are children to other Angular components. (PR "),_c('a',{attrs:{"href":"https://github.com/CATcher-org/CATcher/pull/861"}},[_v("#861")]),_v(")")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://www.kiltandcode.com/2020/08/13/show-validation-error-messages-for-reactive-forms-in-angular-9/"}},[_v("Show Validation Error Messages for Reactive Forms in Angular 9")])])]),_v(" "),_c('h3',{attrs:{"id":"lifecycle-hooks-in-angular"}},[_v("Lifecycle Hooks in Angular"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#lifecycle-hooks-in-angular","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("After a component is instantiated, the ts file has lifecycle hooks in the form of methods that initialize or modify the component content or state. These methods are prefixed with "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("ng")]),_v(".")]),_v(" "),_c('p',[_v("The sequence in which these lifecycle hooks are called:")]),_v(" "),_c('ul',[_c('li',[_v("OnChanges")]),_v(" "),_c('li',[_v("OnInit")]),_v(" "),_c('li',[_v("DoCheck - repeated")]),_v(" "),_c('li',[_v("AfterContentInit")]),_v(" "),_c('li',[_v("AfterContentChecked - repeated")]),_v(" "),_c('li',[_v("AfterViewInit")]),_v(" "),_c('li',[_v("AfterViewChecked - repeated")]),_v(" "),_c('li',[_v("OnDestroy")])]),_v(" "),_c('p',[_v("Most notably used is "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("ngOnInit")]),_v(", which used to instantiate the component variables. In CATcher, "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("ngAfterViewInit")]),_v(" is also used to load issues after the component has been initialized.")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/lifecycle-hooks"}},[_v("Lifecycle Hooks")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://blog.logrocket.com/angular-lifecycle-hooks/"}},[_v("Angular lifecycle hooks explained")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/questions/40817336/whats-the-difference-between-ngoninit-and-ngafterviewinit-of-angular2#:~:text=ngOnInit()%20is%20called%20right,its%20children's%20views%2C%20are%20created"}},[_v("What's the difference between ngOnInit and ngAfterViewInit of Angular2?")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://www.youtube.com/watch?v=kKtrHrciIVs&ab_channel=WebTechTalk"}},[_v("Video: Angular - Zero to Hero - Life Cycle Hooks")])])]),_v(" "),_c('h3',{attrs:{"id":"viewchild-in-angular"}},[_v("ViewChild in Angular"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#viewchild-in-angular","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("While html files can add custom child components using custom defined decorators such as "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("")]),_v(", the parent component may need references to these children components to add change content or add interactability. ViewChild, ContentChild are queries to access child components from the parent component.")]),_v(" "),_c('p',[_v("For example:")]),_v(" "),_c('pre',[_c('code',{pre:true,attrs:{"class":"hljs"}},[_c('span',[_v("@ViewChild(ViewIssueComponent, { static: false }) viewIssue: ViewIssueComponent;\n")])])]),_c('h4',{attrs:{"id":"static-vs-dynamic-queries"}},[_v("Static vs Dynamic queries"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#static-vs-dynamic-queries","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Static queries are queries on child components that do not change during runtime, as such result of the reference to the child component can be made available in "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("ngOnInit")]),_v(".")]),_v(" "),_c('p',[_v("Dynamic queries are queries on child components that change during runtime, so reference to child component can only be made available in "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("ngAfterViewInit")]),_v(".")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://blog.angular-university.io/angular-viewchild/"}},[_v("Angular @ViewChild")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/static-query-migration#what-does-this-flag-mean-and-why-is-it-necessary"}},[_v("Static query migration guide")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://www.youtube.com/watch?v=4YmnbGoh49U&ab_channel=AngularConnect"}},[_v("Video: Better concepts, less code in Angular 2 - Victor Savkin and Tobias Bosch")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/questions/34326745/whats-the-difference-between-viewchild-and-contentchild"}},[_v("What's the difference between @ViewChild and @ContentChild?")])])]),_v(" "),_c('h3',{attrs:{"id":"property-binding"}},[_v("Property Binding"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#property-binding","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Square brackets in html tags in angular indicate that the right hand side is a dynamic expression. For example:")]),_v(" "),_c('pre',[_c('code',{pre:true,attrs:{"class":"hljs"}},[_c('span',[_v("\n")]),_c('span',[_v("\n")])])]),_c('p',[_v("The dynamic expression can be evaluated in the context of the corresponding .ts file of the html file.")]),_v(" "),_c('h3',{attrs:{"id":"event-binding"}},[_v("Event binding"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#event-binding","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Parenthesis within html tags, for example "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("(click)")]),_v(" are used to call the component's corresponding method on click. In the example above, "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("$event.stopPropagation()")]),_v(" is a Javascript call that prevents the label "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("Disagree")]),_v(" within the issue bar from being clickable because its parent is clickable. The click event from parent is stopped from propagating to this particular child.")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/property-binding"}},[_v("Angular Doc Property Binding")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://angular.io/guide/event-binding"}},[_v("Angular Doc Event Binding")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/a/35944965"}},[_v("StackOverflow simple summary")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/questions/32315647/javascript-how-does-event-stoppropagation-work"}},[_v("StackOverflow Stop Propagation")])])]),_v(" "),_c('h3',{attrs:{"id":"git-rebase"}},[_v("Git Rebase"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#git-rebase","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Below is a link to a good explanation with visuals to explain rebasing. Rebasing helped to clean the commit history of my main branch after accidental merging with other branches.")]),_v(" "),_c('p',[_v("Resource:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/a/29049698"}},[_v("github fork : your branch is 5 commits ahead how to clean this without pushing")]),_v(" |")])]),_v(" "),_c('h3',{attrs:{"id":"github-file-upload-api-createfile-vs-createtree"}},[_v("Github File Upload API: CreateFile vs CreateTree"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#github-file-upload-api-createfile-vs-createtree","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("The API for committing a single file and committing multiple files at once are different.\nAttempting to do multiple single file commits in a short duration of time will likely cause HttpError to occur. The current recommeded fix is to put in sleep before the next single file commit, or merge multiple single file commits into a single multiple file commit.")]),_v(" "),_c('p',[_v("Resources:")]),_v(" "),_c('ul',[_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/a/58837709"}},[_v("Use Octokit or the GitHub Rest API to upload multiple files")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/a/19732043"}},[_v("GITHub API Issue with file upload")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://gist.github.com/StephanHoyer/91d8175507fcae8fb31a"}},[_v("Committing multiple files code gist Octokat")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://github.com/octokit/octokit.js/issues/1308"}},[_v("Octokit Pushing a tree")])]),_v(" "),_c('li',[_c('a',{attrs:{"href":"https://stackoverflow.com/questions/61583403/edit-multiple-files-in-single-commit-with-github-api"}},[_v("Github API Edit multiple files upload")])])])])]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Observations")]),_v(" from external projects")])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"project-foo-2"}},[_v("Project: Foo"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#project-foo-2","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give an intro to the project here ...")]),_v(" "),_c('h3',{attrs:{"id":"my-contributions-2"}},[_v("My Contributions"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-contributions-2","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give a description of your contributions, including links to relevant PRs")]),_v(" "),_c('h3',{attrs:{"id":"my-learning-record-2"}},[_v("My Learning Record"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-learning-record-2","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give tools/technologies you learned here. Include resources you used, and a brief summary of the resource.")])])])],1)],1),_v(" "),_c('div',[_c('box',[_c('h2',{attrs:{"id":"lee-xiong-jie-isaac"}},[_v("LEE XIONG JIE, ISAAC"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#lee-xiong-jie-isaac","onclick":"event.stopPropagation()"}})]),_v(" "),_c('div',{staticClass:"container"},[_c('div',{staticClass:"row"},[_c('div',{staticClass:"col"},[_c('img',{attrs:{"src":"/2024/students/luminousleek/photo.png","width":"100"}}),_c('br')]),_v(" "),_c('div',{staticClass:"col"},[_c('p',[_c('strong',[_v("GitHub:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://www.github.com/luminousleek"}},[_v("https://www.github.com/luminousleek")])]),_c('br')]),_v(" "),_c('p',[_c('strong',[_v("Projects:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://github.com/CATcher-org/CATcher"}},[_v("CATcher")])])])])])]),_v(" "),_c('p'),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Progress")])])]},proxy:true}])},[_v(" "),_c('div')]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Knowledge gained")])])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"css-flexbox"}},[_v("CSS Flexbox"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#css-flexbox","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Flexbox is used to order, align and space out items in a one dimensional container (i.e. a row or a column), even when the size of the items are unknown or dynamic.")]),_v(" "),_c('p',[_v("Items can be given a default size ("),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-basis")]),_v("), and can also grow ("),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-grow")]),_v(") and shrink ("),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-shrink")]),_v(") according to the extra space in the container (or lack thereof). These three properties can be controlled with the "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex")]),_v(" property, e.g.")]),_v(" "),_c('pre',[_c('code',{pre:true,attrs:{"class":"hljs css"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-selector-class"}},[_v(".item")]),_v(" {\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attribute"}},[_v("flex")]),_v(": "),_c('span',{pre:true,attrs:{"class":"hljs-number"}},[_v("0")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-number"}},[_v("1")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-number"}},[_v("10%")]),_v("\n")]),_c('span',[_v("}\n")])])]),_c('p',[_v("where the three parameters correspond to "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-grow")]),_v(", "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-shrink")]),_v(" and "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-basis")]),_v(" respectively. In this case, the default width of the item is 10% of the width of the container, and it does not grow nor shrink relative to the other items (assuming the other items also have their "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex")]),_v(" property set to "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("0 1 auto")]),_v(").")]),_v(" "),_c('p',[_v("The "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("flex-basis")]),_v(" property can also be set to the "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("content")]),_v(" keyword, where the width of the item is based on the content within the item (e.g. if it contains text, then the width of the item is the length of the text string). This allows for dynamically sized items within the container, which may enable the layout to look cleaner.")]),_v(" "),_c('p',[_v("For a helpful summary of flexbox properties, visit "),_c('a',{attrs:{"href":"https://css-tricks.com/snippets/css/a-guide-to-flexbox/"}},[_v("https://css-tricks.com/snippets/css/a-guide-to-flexbox/")])]),_v(" "),_c('h3',{attrs:{"id":"prettier-husky-and-pretty-quick"}},[_v("Prettier, husky and pretty-quick"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#prettier-husky-and-pretty-quick","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Prettier is a tool to format code according to a given code style automatically. Unlike a linter such as TSLint, prettier only cares about formatting, rather than detecting programming errors. Prettier supports both Typescript and Angular, which CATcher is written in.")]),_v(" "),_c('p',[_v("Since it is quite wasteful to run prettier to format the entire codebase every time a change is made, so a more efficient method is to format the codebase once, and then format only the changes made during each commit.")]),_v(" "),_c('p',[_v("This is where the husky tool comes in, which enables hooks to be run before each commit. The relevant hook here is pretty-quick, and this formats the changed/staged files during each commit. This frees developers from having to fuss with maintaining code formatting or fixing formatting mistakes, leading to less frustration.")]),_v(" "),_c('p',[_v("For more information, visit "),_c('a',{attrs:{"href":"https://prettier.io"}},[_v("Prettier")]),_v(" and "),_c('a',{attrs:{"href":"https://typicode.github.io/husky/"}},[_v("husky")])]),_v(" "),_c('h3',{attrs:{"id":"arcsecond"}},[_v("Arcsecond"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#arcsecond","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_c('a',{attrs:{"href":"https://github.com/francisrstokes/arcsecond"}},[_v("Arcsecond")]),_v(" is a zero-dependency parser combinator library for Javascript that is now being used in CATcher to parse GitGub issues and comments.")]),_v(" "),_c('p',[_v("Previously in order to parse the comments, we used the regex string "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("(${headerString})\\\\s+([\\\\s\\\\S]*?)(?=${headerString}|$)\\gi")]),_v(" which is neither human readable nor maintainable. In addition, this string only finds matches - more regex is used to extract relevant information from the comments.")]),_v(" "),_c('p',[_v("In comparison, arcsecond offers human friendly parsers such as "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("str")]),_v(", "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("char")]),_v(", "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("letters")]),_v(", "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("digits")]),_v(", "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("between")]),_v(" and so on, and these parsers can be composed and run in sequence. This makes building and maintaining parsers much easier. In addition, arcsecond also allows you to extract certain information from a string (as opposed to the entire string) by way of the "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("coroutine")]),_v(" parser.")]),_v(" "),_c('p',[_v("For example, take the string \"Today is Wednesday and the weather is 30 degrees Celsius\", and you want to extract the day (Wednesday) and the temperature (30). One parser that can achieve that is:")]),_v(" "),_c('pre',[_c('code',{pre:true,attrs:{"class":"hljs typescript"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("const")]),_v(" dayWeatherParser = coroutine("),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("function")]),_v("*("),_c('span',{pre:true,attrs:{"class":"hljs-params"}}),_v(") ")]),_v("{\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("yield")]),_v(" str("),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"Today is \"")]),_v("); "),_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_v("// parse and ignore")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("const")]),_v(" day = "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("yield")]),_v(" letters; "),_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_v("// parse 'Wednesday' and store")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("yield")]),_v(" str("),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\" and the weather is \"")]),_v("); "),_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_v("// parse and ignore")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("const")]),_v(" temperature = "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("yield")]),_v(" digits; "),_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_v("// parse '30' and store")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("yield")]),_v(" str("),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\" degrees Celsius\"")]),_v("); "),_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_v("// parse and ignore")]),_v("\n")]),_c('span',[_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("return")]),_v(" {\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("day")]),_v(": day,\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("temperature")]),_v(": temperature\n")]),_c('span',[_v(" }\n")]),_c('span',[_v("})\n")])])]),_c('p',[_v("This allows us to build complex and versatile parsers easily, yet in a way that is clear and understandable. For more information, check out the "),_c('a',{attrs:{"href":"https://github.com/francisrstokes/arcsecond/blob/master/tutorial/tutorial-part-1.md"}},[_v("tutorial here")])]),_v(" "),_c('h3',{attrs:{"id":"jasmine"}},[_v("Jasmine"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#jasmine","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_c('a',{attrs:{"href":"https://jasmine.github.io/"}},[_v("Jasmine")]),_v(" is a testing framework for Javascript code. In Jasmine, test suites are functions in "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("describe")]),_v(" blocks, and each spec is also a function in an "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("it")]),_v(" block.")]),_v(" "),_c('p',[_v("For example, here is a suite that tests a certain function:")]),_v(" "),_c('pre',[_c('code',{pre:true,attrs:{"class":"hljs typescript"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("function")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-title"}},[_v("testMethod")]),_v("("),_c('span',{pre:true,attrs:{"class":"hljs-params"}}),_v(") ")]),_v("{\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("return")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-literal"}},[_v("true")]),_v(";\n")]),_c('span',[_v("}\n")]),_c('span',[_v("\n")]),_c('span',[_v("describe("),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"testMethod suite\"")]),_v(", "),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_v("() =>")]),_v(" {\n")]),_c('span',[_v(" it("),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"testMethod should return true\"")]),_v(", "),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_v("() =>")]),_v(" {\n")]),_c('span',[_v(" expect(testMethod()).toBe("),_c('span',{pre:true,attrs:{"class":"hljs-literal"}},[_v("true")]),_v(");\n")]),_c('span',[_v(" });\n")]),_c('span',[_v("});\n")])])]),_c('p',[_v("Expectations are built with the function "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("expect")]),_v(" which takes a value ("),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("testMethod()")]),_v(" in the example above), and is chained with a "),_c('a',{attrs:{"href":"https://jasmine.github.io/api/edge/matchers.html"}},[_v("Matcher")]),_v(" such as "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("toBe")]),_v(", "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("toEqual")]),_v(" or "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("toBeGreaterThan")]),_v(". This provides greater flexibility than say JUnit's assert methods, since one assert method corresponds to one condition.")]),_v(" "),_c('p',[_v("Since test suites and specs are functions, normal Javascript scoping rules apply, so variables can be shared between specs. In addition, there are separate setup and teardown methods such as "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("beforeEach")]),_v(" and "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("afterAll")]),_v(".")]),_v(" "),_c('p',[_v("For more information, check out the "),_c('a',{attrs:{"href":"https://jasmine.github.io/tutorials/your_first_suite"}},[_v("Your First Suite tutorial here")])])])]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Observations")]),_v(" from external projects")])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"project-foo-3"}},[_v("Project: Foo"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#project-foo-3","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give an intro to the project here ...")]),_v(" "),_c('h3',{attrs:{"id":"my-contributions-3"}},[_v("My Contributions"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-contributions-3","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give a description of your contributions, including links to relevant PRs")]),_v(" "),_c('h3',{attrs:{"id":"my-learning-record-3"}},[_v("My Learning Record"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-learning-record-3","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give tools/technologies you learned here. Include resources you used, and a brief summary of the resource.")])])])],1)],1),_v(" "),_c('div',[_c('box',[_c('h2',{attrs:{"id":"vignesh-sankar-iyer"}},[_v("VIGNESH SANKAR IYER"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#vignesh-sankar-iyer","onclick":"event.stopPropagation()"}})]),_v(" "),_c('div',{staticClass:"container"},[_c('div',{staticClass:"row"},[_c('div',{staticClass:"col"},[_c('img',{attrs:{"src":"/2024/students/vigneshsankariyer1234567890/photo.png","width":"100"}}),_c('br')]),_v(" "),_c('div',{staticClass:"col"},[_c('p',[_c('strong',[_v("GitHub:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://github.com/vigneshsankariyer1234567890"}},[_v("https://github.com/vigneshsankariyer1234567890")])]),_c('br')]),_v(" "),_c('p',[_c('strong',[_v("Projects:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://github.com/CATcher-org/CATcher"}},[_v("CATcher")])])])])])]),_v(" "),_c('p'),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Progress")])])]},proxy:true}])},[_v(" "),_c('div')]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Knowledge gained")])])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"angular"}},[_v("Angular"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#angular","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Having had experience in mainly React and NodeJS projects earlier, I was overall more used to creating projects with Functional Components, rather than Class Components as with Angular. However, I realised that one of the key aspects of frontend frameworks, namely reactivity, was in fact the main drivers of development of such frameworks in the first place!")]),_v(" "),_c('p',[_v("In fact, even React were originally championing the idea of Class Components in order to isolate various web components into areas or responsibility, following rule number 1 of Software Engineering: Single Responsibility. However, while React is largely unopinionated in how you structure your code with regards to the coupling of business logic and HTML, Angular differs by dictating where and how you structure your components.")]),_v(" "),_c('p',[_v("Angular separates components into modules which comprise of 3 to 4 files:")]),_v(" "),_c('ul',[_c('li',[_v("Components, which are necessarily TypeScript classes which have the "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("@Component")]),_v(" decorator;")]),_v(" "),_c('li',[_v("Templates, which dictate the HTML that is produced and rendered by the component;")]),_v(" "),_c('li',[_v("Styles, which dictate the type of styling to apply to the component.")]),_v(" "),_c('li',[_v("Module, which indicate the modules or services that are to be imported by the component. Interestingly,")])]),_v(" "),_c('p',[_v("On the other hand, React only dictates that class components should produce some sort of HTML using the render function. Even this is removed with the introduction of Functional Components that are simply functions which render and produce some HTML. React introduces hooks which are often used by developers to manage some state at the component level, using functions with side effects.")]),_v(" "),_c('p',[_v("Each method has its positives and negatives. Because of its opinionated nature, Angular makes it easy to standardize frontend coding standards and pattern across an entire enterprise, making it an apt choice to use as a tool for OSS development. On the other hand, React allows you to develop code more quickly, with more attention needed to be paid at the rendering lifecycles in order to let the Virtual DOM know when a particular component needs to be rendered again. On top of this, Angular wholely separates business logic from rendered HTML, whereas React takes the does not make this distinction.")]),_v(" "),_c('p',[_v("Another key point is how React and Angular differentiate in providing context (sharing or passing down state between different branches of the DOM tree). React has its own Context API that is used to share some sort of state between different components, whereas Angular does this by the "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("providers")]),_v(" declaration in the module folder, which results in a set of singletons that are shared by components that exist below it in the tree.")]),_v(" "),_c('h3',{attrs:{"id":"rxjs"}},[_v("RxJS"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#rxjs","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("I also picked up RxJS along the way, which was Angular's answer to creating reactive components. RxJS essentially deals with asynchronous pipe/filter, publisher/subscriber behavior which allows values to change and other components or functions to subscribe to these changes. This works considering Angular's Change Detection strategy which I will explain later.")]),_v(" "),_c('p',[_v("In comparison, React introduced and adopted hooks to encapsulate the behavior of having to rerender. React does this by operating on a Virtual DOM, and appropriately rerendering components and their children in patches when a change was detected. On the other hand, Angular does not have any abstraction to operate and rerender components whose state have changed. Instead, Angular uses a Change Detection Strategy which can be configured by the user (either onPush or Default). Angular Change Detection works by using Zone.js and activating after every async action performed. CD traversal starts at the root component (usually App) and works its way down the component tree updating the DOM as needed. What's happening under the hood is that browser events are registered into Zone.js - Angular's mechanism for orchestrating async events - which emits changes after initial template bindings are created.\n...")])])]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Observations")]),_v(" from external projects")])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"project-foo-4"}},[_v("Project: Foo"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#project-foo-4","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give an intro to the project here ...")]),_v(" "),_c('h3',{attrs:{"id":"my-contributions-4"}},[_v("My Contributions"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-contributions-4","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give a description of your contributions, including links to relevant PRs")]),_v(" "),_c('h3',{attrs:{"id":"my-learning-record-4"}},[_v("My Learning Record"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#my-learning-record-4","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("Give tools/technologies you learned here. Include resources you used, and a brief summary of the resource.")])])])],1)],1),_v(" "),_m(10),_v(" "),_c('div',[_c('box',[_c('h2',{attrs:{"id":"lee-wei-david"}},[_v("LEE WEI, DAVID"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#lee-wei-david","onclick":"event.stopPropagation()"}})]),_v(" "),_c('div',{staticClass:"container"},[_c('div',{staticClass:"row"},[_c('div',{staticClass:"col"},[_c('img',{attrs:{"src":"/2024/students/itsyme/photo.png","width":"100"}}),_c('br')]),_v(" "),_c('div',{staticClass:"col"},[_c('p',[_c('strong',[_v("GitHub:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://www.github.com/itsyme"}},[_v("https://www.github.com/itsyme")])]),_c('br')]),_v(" "),_c('p',[_c('strong',[_v("Projects:")]),_v(" "),_c('span',[_c('a',{attrs:{"href":"https://github.com/MarkBind/markbind"}},[_v("MarkBind")])])])])])]),_v(" "),_c('p'),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Progress")])])]},proxy:true}])},[_v(" "),_c('div')]),_v(" "),_c('panel',{attrs:{"minimized":""},scopedSlots:_u([{key:"header",fn:function(){return [_c('p',[_c('strong',[_v("Knowledge gained")])])]},proxy:true}])},[_v(" "),_c('div',[_c('h3',{attrs:{"id":"vue-js"}},[_v("Vue.js"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#vue-js","onclick":"event.stopPropagation()"}})]),_v(" "),_c('p',[_v("One of the largest takeaways from working with MarkBind in the last semester has been Vue.js, an open-source front-end framework that MarkBind uses to build it's UI components. Previously, only knowing the React.js framework, Vue.js is a handy addition to my arsenal. The basics of Vue.js was rather simple to pick up. Reading the "),_c('a',{attrs:{"href":"https://vuejs.org/guide/introduction.html"}},[_v("Vue.js documentation")]),_v(", and referencing examples of already implemented Vue components in MarkBind, I quickly understood the use of "),_c('code',{pre:true,attrs:{"class":"hljs inline no-lang"}},[_v("