From 9711b6b6888bd85bf1f33b81a5a771599b5021c8 Mon Sep 17 00:00:00 2001 From: 109000102 Date: Thu, 27 Feb 2025 15:17:25 +0800 Subject: [PATCH 1/7] lab0 --- lab0/lab0.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lab0/lab0.js b/lab0/lab0.js index e69de29..940a3ff 100644 --- a/lab0/lab0.js +++ b/lab0/lab0.js @@ -0,0 +1 @@ +console.log("Hello world!"); From 64cfbc8714bb1a52a0f79058dbe897c2f731c1b7 Mon Sep 17 00:00:00 2001 From: AxelHowe Date: Wed, 5 Mar 2025 21:45:27 +0800 Subject: [PATCH 2/7] feat: lab1 --- lab1/README.md | 22 +++++++++++++++++++ lab1/main.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ lab1/main_test.js | 23 ++++++++++++++++++++ lab1/validate.sh | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 lab1/README.md create mode 100644 lab1/main.js create mode 100644 lab1/main_test.js create mode 100755 lab1/validate.sh diff --git a/lab1/README.md b/lab1/README.md new file mode 100644 index 0000000..df0ab9f --- /dev/null +++ b/lab1/README.md @@ -0,0 +1,22 @@ +# Lab1 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Requirement + +1. Write test cases in `main_test.js` and achieve 100% code coverage. (100%) + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 311XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab1/main.js b/lab1/main.js new file mode 100644 index 0000000..c9aed9f --- /dev/null +++ b/lab1/main.js @@ -0,0 +1,55 @@ +// NOTICE: DO NOT MODIFY THE CODE IN THIS FILE +// But you can uncomment code below and run this file to understand how to use the classes + +class MyClass { + constructor() { + this.students = []; + } + + addStudent(student) { + if (!(student instanceof Student)) { + return -1; + } + this.students.push(student); + return this.students.length - 1; + } + + getStudentById(id) { + if (id < 0 || id >= this.students.length) { + return null; + } + return this.students[id]; + } +} + +class Student { + constructor() { + this.name = undefined; + } + + setName(userName) { + if (typeof userName !== 'string') { + return; + } + this.name = userName; + } + + getName() { + if (this.name === undefined) { + return ''; + } + return this.name; + } +} + +// const myClass = new MyClass(); +// const names = ['John', 'Jane', 'Doe', 'Smith']; +// names.forEach(name => { +// const student = new Student(); +// student.setName(name); +// const newStudentId = myClass.addStudent(student); +// const newStudentName = myClass.getStudentById(newStudentId).getName(); +// console.log('[+] Added student with id: %d, name: %s', newStudentId, newStudentName); +// }); + +module.exports = { MyClass, Student }; \ No newline at end of file diff --git a/lab1/main_test.js b/lab1/main_test.js new file mode 100644 index 0000000..74a716b --- /dev/null +++ b/lab1/main_test.js @@ -0,0 +1,23 @@ +const test = require('node:test'); +const assert = require('assert'); +const { MyClass, Student } = require('./main'); + +test("Test MyClass's addStudent", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test MyClass's getStudentById", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test Student's setName", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test Student's getName", () => { + // TODO + throw new Error("Test not implemented"); +}); \ No newline at end of file diff --git a/lab1/validate.sh b/lab1/validate.sh new file mode 100755 index 0000000..8f2fcd4 --- /dev/null +++ b/lab1/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab1-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails." + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%, should be 100%." + exit 1 + else + echo "[V] Coverage is 100%, great job!" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file From 28d8fdd8e246f3bc76f5ef580d4f7ed9a0d3ff55 Mon Sep 17 00:00:00 2001 From: 109000102 Date: Thu, 6 Mar 2025 15:13:28 +0800 Subject: [PATCH 3/7] lab1 --- lab1/main_test.js | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/lab1/main_test.js b/lab1/main_test.js index 74a716b..0ce0e0a 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -4,20 +4,51 @@ const { MyClass, Student } = require('./main'); test("Test MyClass's addStudent", () => { // TODO - throw new Error("Test not implemented"); + const myClass = new MyClass(); + const student = new Student(); + + const studentId = myClass.addStudent(student); + // const name = ['John']; + assert.strictEqual(studentId, 0); + + assert.strictEqual(myClass.addStudent({}), -1); + // throw new Error("Test not implemented"); }); test("Test MyClass's getStudentById", () => { // TODO - throw new Error("Test not implemented"); + const myClass = new MyClass(); + const student = new Student(); + student.setName("John"); + const studentId = myClass.addStudent(student); + + const retrievedStudent = myClass.getStudentById(studentId); + assert.strictEqual(retrievedStudent, student); + + assert.strictEqual(myClass.getStudentById(-1), null); + assert.strictEqual(myClass.getStudentById(100), null); + // throw new Error("Test not implemented"); }); test("Test Student's setName", () => { // TODO - throw new Error("Test not implemented"); + const student = new Student(); + + student.setName("Alice"); + assert.strictEqual(student.getName(), "Alice"); + + student.setName(123); + assert.strictEqual(student.getName(), "Alice"); + // throw new Error("Test not implemented"); }); test("Test Student's getName", () => { // TODO - throw new Error("Test not implemented"); + const student = new Student(); + + assert.strictEqual(student.getName(), ""); + + student.setName("Bob"); + assert.strictEqual(student.getName(), "Bob"); + // throw new Error("Test not implemented"); }); \ No newline at end of file From 08c3a4ee01e3017a1ce94e0b2afd7c25c0a73a42 Mon Sep 17 00:00:00 2001 From: AxelHowe Date: Wed, 12 Mar 2025 20:52:47 +0800 Subject: [PATCH 4/7] feat: lab2 --- lab1/main_test.js | 39 +++-------------------- lab2/README.md | 22 +++++++++++++ lab2/main.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++ lab2/main_test.js | 6 ++++ lab2/validate.sh | 38 ++++++++++++++++++++++ 5 files changed, 151 insertions(+), 35 deletions(-) create mode 100644 lab2/README.md create mode 100644 lab2/main.js create mode 100644 lab2/main_test.js create mode 100755 lab2/validate.sh diff --git a/lab1/main_test.js b/lab1/main_test.js index 0ce0e0a..74a716b 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -4,51 +4,20 @@ const { MyClass, Student } = require('./main'); test("Test MyClass's addStudent", () => { // TODO - const myClass = new MyClass(); - const student = new Student(); - - const studentId = myClass.addStudent(student); - // const name = ['John']; - assert.strictEqual(studentId, 0); - - assert.strictEqual(myClass.addStudent({}), -1); - // throw new Error("Test not implemented"); + throw new Error("Test not implemented"); }); test("Test MyClass's getStudentById", () => { // TODO - const myClass = new MyClass(); - const student = new Student(); - student.setName("John"); - const studentId = myClass.addStudent(student); - - const retrievedStudent = myClass.getStudentById(studentId); - assert.strictEqual(retrievedStudent, student); - - assert.strictEqual(myClass.getStudentById(-1), null); - assert.strictEqual(myClass.getStudentById(100), null); - // throw new Error("Test not implemented"); + throw new Error("Test not implemented"); }); test("Test Student's setName", () => { // TODO - const student = new Student(); - - student.setName("Alice"); - assert.strictEqual(student.getName(), "Alice"); - - student.setName(123); - assert.strictEqual(student.getName(), "Alice"); - // throw new Error("Test not implemented"); + throw new Error("Test not implemented"); }); test("Test Student's getName", () => { // TODO - const student = new Student(); - - assert.strictEqual(student.getName(), ""); - - student.setName("Bob"); - assert.strictEqual(student.getName(), "Bob"); - // throw new Error("Test not implemented"); + throw new Error("Test not implemented"); }); \ No newline at end of file diff --git a/lab2/README.md b/lab2/README.md new file mode 100644 index 0000000..60a9c80 --- /dev/null +++ b/lab2/README.md @@ -0,0 +1,22 @@ +# Lab2 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Requirement + +1. Write test cases in `main_test.js` and achieve 100% code coverage. Remember to use Mock, Spy, or Stub when necessary, you need to at least use one of them in your test cases. (100%) + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 311XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab2/main.js b/lab2/main.js new file mode 100644 index 0000000..2e159e7 --- /dev/null +++ b/lab2/main.js @@ -0,0 +1,81 @@ +const fs = require('fs'); +const util = require('util'); +const readFile = util.promisify(fs.readFile); + +class MailSystem { + write(name) { + console.log('--write mail for ' + name + '--'); + const context = 'Congrats, ' + name + '!'; + return context; + } + + send(name, context) { + console.log('--send mail to ' + name + '--'); + // Interact with mail system and send mail + // random success or failure + const success = Math.random() > 0.5; + if (success) { + console.log('mail sent'); + } else { + console.log('mail failed'); + } + return success; + } +} + +class Application { + constructor() { + this.people = []; + this.selected = []; + this.mailSystem = new MailSystem(); + this.getNames().then(([people, selected]) => { + this.people = people; + this.selected = selected; + }); + } + + async getNames() { + const data = await readFile('name_list.txt', 'utf8'); + const people = data.split('\n'); + const selected = []; + return [people, selected]; + } + + getRandomPerson() { + const i = Math.floor(Math.random() * this.people.length); + return this.people[i]; + } + + selectNextPerson() { + console.log('--select next person--'); + if (this.people.length === this.selected.length) { + console.log('all selected'); + return null; + } + let person = this.getRandomPerson(); + while (this.selected.includes(person)) { + person = this.getRandomPerson(); + } + this.selected.push(person); + return person; + } + + notifySelected() { + console.log('--notify selected--'); + for (const x of this.selected) { + const context = this.mailSystem.write(x); + this.mailSystem.send(x, context); + } + } +} + +// const app = new Application(); +// app.selectNextPerson(); +// app.selectNextPerson(); +// app.selectNextPerson(); +// app.notifySelected(); + +module.exports = { + Application, + MailSystem, +}; \ No newline at end of file diff --git a/lab2/main_test.js b/lab2/main_test.js new file mode 100644 index 0000000..5034468 --- /dev/null +++ b/lab2/main_test.js @@ -0,0 +1,6 @@ +const test = require('node:test'); +const assert = require('assert'); +const { Application, MailSystem } = require('./main'); + +// TODO: write your tests here +// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file diff --git a/lab2/validate.sh b/lab2/validate.sh new file mode 100755 index 0000000..13b53ed --- /dev/null +++ b/lab2/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab2-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails" + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%" + exit 1 + else + echo "[V] Coverage is 100%" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file From 9cfc419796ced2cfdc7ab6a779e74676fd2896f8 Mon Sep 17 00:00:00 2001 From: 109000102 Date: Fri, 14 Mar 2025 22:51:39 +0800 Subject: [PATCH 5/7] lab2 --- lab2/main_test.js | 184 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 1 deletion(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index 5034468..34eb87a 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -3,4 +3,186 @@ const assert = require('assert'); const { Application, MailSystem } = require('./main'); // TODO: write your tests here -// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file +// Remember to use Stub, Mock, and Spy when necessary +const fs = require('node:fs'); +const util = require('util'); +const writeFile = util.promisify(fs.writeFile); +const unlinkFile = util.promisify(fs.unlink); + +async function createTestFile(content = "Alice\nBob\nCharlie") { + await writeFile("name_list.txt", content, 'utf-8'); +} + +async function removeTestFile() { + try { + await unlinkFile("name_list.txt"); + } catch (error) { + // Ignore errors + } +} + +test.before(async () => { + await createTestFile(); +}); + +test.after(async () => { + await removeTestFile(); +}); + +// Tests for MailSystem class +test('MailSystem.write should return congratulatory message', (t) => { + const mailSystem = new MailSystem(); + const result = mailSystem.write('John'); + assert.strictEqual(result, 'Congrats, John!'); +}); + +test('MailSystem.send should return boolean indicating success', (t) => { + const mailSystem = new MailSystem(); + + const originalRandom = Math.random; + + // Test success case + Math.random = () => 0.6; // return true + const successResult = mailSystem.send('John', 'Congrats, John!'); + assert.strictEqual(successResult, true); + + // Test failure case + Math.random = () => 0.4; // return false + const failureResult = mailSystem.send('John', 'Congrats, John!'); + assert.strictEqual(failureResult, false); + + Math.random = originalRandom; +}); + +test('Application constructor should initialize properties', async (t) => { + await createTestFile("Alice\nBob\nCharlie"); + const app = new Application(); + + await new Promise(resolve => setTimeout(resolve, 10)); + + assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie']); + assert.deepStrictEqual(app.selected, []); + assert.ok(app.mailSystem instanceof MailSystem); +}); + +test('getNames should read and parse names from file', async (t) => { + await createTestFile("Dave\nEve\nFrank"); + + const app = new Application(); + const [people, selected] = await app.getNames(); + + assert.deepStrictEqual(people, ['Dave', 'Eve', 'Frank']); + assert.deepStrictEqual(selected, []); +}); + +test('getRandomPerson should return a person from the people array', async (t) => { + const app = new Application(); + + await new Promise(resolve => setTimeout(resolve, 10)); + + app.people = ['Alice', 'Bob', 'Charlie']; + + const originalRandom = Math.random; + const originalFloor = Math.floor; + + // Create a spy + let floorCallCount = 0; + Math.floor = (num) => { + floorCallCount++; + return originalFloor(num); + }; + + Math.random = () => 0; //select idx 0 + assert.strictEqual(app.getRandomPerson(), 'Alice'); + + Math.random = () => 0.34; // select idx 1 + assert.strictEqual(app.getRandomPerson(), 'Bob'); + + Math.random = () => 0.67; // select idx 2 + assert.strictEqual(app.getRandomPerson(), 'Charlie'); + + assert.strictEqual(floorCallCount, 3); + + Math.random = originalRandom; + Math.floor = originalFloor; +}); + +test('selectNextPerson should select a random unselected person', async (t) => { + const app = new Application(); + await new Promise(resolve => setTimeout(resolve, 10)); + + app.people = ['Alice', 'Bob', 'Charlie']; + app.selected = []; + + const originalGetRandomPerson = app.getRandomPerson; + let randomPersonCalls = 0; + + app.getRandomPerson = () => { + randomPersonCalls++; + if (randomPersonCalls === 1) return 'Bob'; + if (randomPersonCalls === 2) return 'Bob'; + if (randomPersonCalls === 3) return 'Alice'; + return 'Charlie'; + }; + + const result = app.selectNextPerson(); + assert.strictEqual(result, 'Bob'); + assert.deepStrictEqual(app.selected, ['Bob']); + + const secondResult = app.selectNextPerson(); + assert.strictEqual(secondResult, 'Alice'); + assert.deepStrictEqual(app.selected, ['Bob', 'Alice']); + + app.getRandomPerson = originalGetRandomPerson; +}); + +test('selectNextPerson should return null when all people are selected', async (t) => { + const app = new Application(); + await new Promise(resolve => setTimeout(resolve, 10)); + + app.people = ['Alice', 'Bob']; + app.selected = ['Alice', 'Bob']; + + const result = app.selectNextPerson(); + + assert.strictEqual(result, null); +}); + +test('notifySelected should send mail to all selected people', async (t) => { + const app = new Application(); + await new Promise(resolve => setTimeout(resolve, 10)); + + app.selected = ['Alice', 'Bob']; + + const originalWrite = app.mailSystem.write; + const originalSend = app.mailSystem.send; + + const writeCalls = []; + const sendCalls = []; + + app.mailSystem.write = (name) => { + writeCalls.push(name); + return `Congrats, ${name}!`; + }; + + app.mailSystem.send = (name, context) => { + sendCalls.push({ name, context }); + return true; + }; + + app.notifySelected(); + + assert.strictEqual(writeCalls.length, 2); + assert.strictEqual(sendCalls.length, 2); + + assert.strictEqual(writeCalls[0], 'Alice'); + assert.strictEqual(writeCalls[1], 'Bob'); + + assert.strictEqual(sendCalls[0].name, 'Alice'); + assert.strictEqual(sendCalls[0].context, 'Congrats, Alice!'); + assert.strictEqual(sendCalls[1].name, 'Bob'); + assert.strictEqual(sendCalls[1].context, 'Congrats, Bob!'); + + app.mailSystem.write = originalWrite; + app.mailSystem.send = originalSend; +}); \ No newline at end of file From 880560b3ea85567e79bb3b8cbcafa42b78eefde7 Mon Sep 17 00:00:00 2001 From: CTHua Date: Thu, 20 Mar 2025 15:35:06 +0800 Subject: [PATCH 6/7] feat: lab3 --- lab2/main_test.js | 184 +--------------------------------------------- lab3/README.md | 29 ++++++++ lab3/main.js | 34 +++++++++ lab3/main_test.js | 5 ++ lab3/validate.sh | 38 ++++++++++ 5 files changed, 107 insertions(+), 183 deletions(-) create mode 100644 lab3/README.md create mode 100644 lab3/main.js create mode 100644 lab3/main_test.js create mode 100755 lab3/validate.sh diff --git a/lab2/main_test.js b/lab2/main_test.js index 34eb87a..5034468 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -3,186 +3,4 @@ const assert = require('assert'); const { Application, MailSystem } = require('./main'); // TODO: write your tests here -// Remember to use Stub, Mock, and Spy when necessary -const fs = require('node:fs'); -const util = require('util'); -const writeFile = util.promisify(fs.writeFile); -const unlinkFile = util.promisify(fs.unlink); - -async function createTestFile(content = "Alice\nBob\nCharlie") { - await writeFile("name_list.txt", content, 'utf-8'); -} - -async function removeTestFile() { - try { - await unlinkFile("name_list.txt"); - } catch (error) { - // Ignore errors - } -} - -test.before(async () => { - await createTestFile(); -}); - -test.after(async () => { - await removeTestFile(); -}); - -// Tests for MailSystem class -test('MailSystem.write should return congratulatory message', (t) => { - const mailSystem = new MailSystem(); - const result = mailSystem.write('John'); - assert.strictEqual(result, 'Congrats, John!'); -}); - -test('MailSystem.send should return boolean indicating success', (t) => { - const mailSystem = new MailSystem(); - - const originalRandom = Math.random; - - // Test success case - Math.random = () => 0.6; // return true - const successResult = mailSystem.send('John', 'Congrats, John!'); - assert.strictEqual(successResult, true); - - // Test failure case - Math.random = () => 0.4; // return false - const failureResult = mailSystem.send('John', 'Congrats, John!'); - assert.strictEqual(failureResult, false); - - Math.random = originalRandom; -}); - -test('Application constructor should initialize properties', async (t) => { - await createTestFile("Alice\nBob\nCharlie"); - const app = new Application(); - - await new Promise(resolve => setTimeout(resolve, 10)); - - assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie']); - assert.deepStrictEqual(app.selected, []); - assert.ok(app.mailSystem instanceof MailSystem); -}); - -test('getNames should read and parse names from file', async (t) => { - await createTestFile("Dave\nEve\nFrank"); - - const app = new Application(); - const [people, selected] = await app.getNames(); - - assert.deepStrictEqual(people, ['Dave', 'Eve', 'Frank']); - assert.deepStrictEqual(selected, []); -}); - -test('getRandomPerson should return a person from the people array', async (t) => { - const app = new Application(); - - await new Promise(resolve => setTimeout(resolve, 10)); - - app.people = ['Alice', 'Bob', 'Charlie']; - - const originalRandom = Math.random; - const originalFloor = Math.floor; - - // Create a spy - let floorCallCount = 0; - Math.floor = (num) => { - floorCallCount++; - return originalFloor(num); - }; - - Math.random = () => 0; //select idx 0 - assert.strictEqual(app.getRandomPerson(), 'Alice'); - - Math.random = () => 0.34; // select idx 1 - assert.strictEqual(app.getRandomPerson(), 'Bob'); - - Math.random = () => 0.67; // select idx 2 - assert.strictEqual(app.getRandomPerson(), 'Charlie'); - - assert.strictEqual(floorCallCount, 3); - - Math.random = originalRandom; - Math.floor = originalFloor; -}); - -test('selectNextPerson should select a random unselected person', async (t) => { - const app = new Application(); - await new Promise(resolve => setTimeout(resolve, 10)); - - app.people = ['Alice', 'Bob', 'Charlie']; - app.selected = []; - - const originalGetRandomPerson = app.getRandomPerson; - let randomPersonCalls = 0; - - app.getRandomPerson = () => { - randomPersonCalls++; - if (randomPersonCalls === 1) return 'Bob'; - if (randomPersonCalls === 2) return 'Bob'; - if (randomPersonCalls === 3) return 'Alice'; - return 'Charlie'; - }; - - const result = app.selectNextPerson(); - assert.strictEqual(result, 'Bob'); - assert.deepStrictEqual(app.selected, ['Bob']); - - const secondResult = app.selectNextPerson(); - assert.strictEqual(secondResult, 'Alice'); - assert.deepStrictEqual(app.selected, ['Bob', 'Alice']); - - app.getRandomPerson = originalGetRandomPerson; -}); - -test('selectNextPerson should return null when all people are selected', async (t) => { - const app = new Application(); - await new Promise(resolve => setTimeout(resolve, 10)); - - app.people = ['Alice', 'Bob']; - app.selected = ['Alice', 'Bob']; - - const result = app.selectNextPerson(); - - assert.strictEqual(result, null); -}); - -test('notifySelected should send mail to all selected people', async (t) => { - const app = new Application(); - await new Promise(resolve => setTimeout(resolve, 10)); - - app.selected = ['Alice', 'Bob']; - - const originalWrite = app.mailSystem.write; - const originalSend = app.mailSystem.send; - - const writeCalls = []; - const sendCalls = []; - - app.mailSystem.write = (name) => { - writeCalls.push(name); - return `Congrats, ${name}!`; - }; - - app.mailSystem.send = (name, context) => { - sendCalls.push({ name, context }); - return true; - }; - - app.notifySelected(); - - assert.strictEqual(writeCalls.length, 2); - assert.strictEqual(sendCalls.length, 2); - - assert.strictEqual(writeCalls[0], 'Alice'); - assert.strictEqual(writeCalls[1], 'Bob'); - - assert.strictEqual(sendCalls[0].name, 'Alice'); - assert.strictEqual(sendCalls[0].context, 'Congrats, Alice!'); - assert.strictEqual(sendCalls[1].name, 'Bob'); - assert.strictEqual(sendCalls[1].context, 'Congrats, Bob!'); - - app.mailSystem.write = originalWrite; - app.mailSystem.send = originalSend; -}); \ No newline at end of file +// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file diff --git a/lab3/README.md b/lab3/README.md new file mode 100644 index 0000000..b591ab8 --- /dev/null +++ b/lab3/README.md @@ -0,0 +1,29 @@ +# Lab3 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Preparation (Important!!!) + +1. Sync fork on GitHub +2. `git checkout -b lab3` (**NOT** your student ID !!!) + +## Requirement + +1. (40%) Write test cases in `main_test.js` and achieve 100% code coverage. +2. (30%) For each function, parameterize their testcases to test the error-results. +3. (30%) For each function, use at least 3 parameterized testcases to test the non-error-results. + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 312XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab3/main.js b/lab3/main.js new file mode 100644 index 0000000..cee5de7 --- /dev/null +++ b/lab3/main.js @@ -0,0 +1,34 @@ +class Calculator { + exp(x) { + if (!Number.isFinite(x)) { + throw Error('unsupported operand type'); + } + const result = Math.exp(x); + if (result === Infinity) { + throw Error('overflow'); + } + return result; + } + + log(x) { + if (!Number.isFinite(x)) { + throw Error('unsupported operand type'); + } + const result = Math.log(x); + if (result === -Infinity) { + throw Error('math domain error (1)'); + } + if (Number.isNaN(result)) { + throw Error('math domain error (2)'); + } + return result; + } +} + +// const calculator = new Calculator(); +// console.log(calculator.exp(87)); +// console.log(calculator.log(48763)); + +module.exports = { + Calculator +}; \ No newline at end of file diff --git a/lab3/main_test.js b/lab3/main_test.js new file mode 100644 index 0000000..e6d6414 --- /dev/null +++ b/lab3/main_test.js @@ -0,0 +1,5 @@ +const {describe, it} = require('node:test'); +const assert = require('assert'); +const { Calculator } = require('./main'); + +// TODO: write your tests here diff --git a/lab3/validate.sh b/lab3/validate.sh new file mode 100755 index 0000000..7a758fb --- /dev/null +++ b/lab3/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab3-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails" + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%" + exit 1 + else + echo "[V] Coverage is 100%" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file From cb6ad76ae6867c1ed8a8e3a7d2fd2796e1724d44 Mon Sep 17 00:00:00 2001 From: 109000102 Date: Thu, 20 Mar 2025 16:01:22 +0800 Subject: [PATCH 7/7] lab3 --- lab3/README.md | 29 ++++++++++++++++++++ lab3/main.js | 34 +++++++++++++++++++++++ lab3/main_test.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++ lab3/validate.sh | 38 +++++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 lab3/README.md create mode 100644 lab3/main.js create mode 100644 lab3/main_test.js create mode 100755 lab3/validate.sh diff --git a/lab3/README.md b/lab3/README.md new file mode 100644 index 0000000..b591ab8 --- /dev/null +++ b/lab3/README.md @@ -0,0 +1,29 @@ +# Lab3 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Preparation (Important!!!) + +1. Sync fork on GitHub +2. `git checkout -b lab3` (**NOT** your student ID !!!) + +## Requirement + +1. (40%) Write test cases in `main_test.js` and achieve 100% code coverage. +2. (30%) For each function, parameterize their testcases to test the error-results. +3. (30%) For each function, use at least 3 parameterized testcases to test the non-error-results. + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 312XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab3/main.js b/lab3/main.js new file mode 100644 index 0000000..cee5de7 --- /dev/null +++ b/lab3/main.js @@ -0,0 +1,34 @@ +class Calculator { + exp(x) { + if (!Number.isFinite(x)) { + throw Error('unsupported operand type'); + } + const result = Math.exp(x); + if (result === Infinity) { + throw Error('overflow'); + } + return result; + } + + log(x) { + if (!Number.isFinite(x)) { + throw Error('unsupported operand type'); + } + const result = Math.log(x); + if (result === -Infinity) { + throw Error('math domain error (1)'); + } + if (Number.isNaN(result)) { + throw Error('math domain error (2)'); + } + return result; + } +} + +// const calculator = new Calculator(); +// console.log(calculator.exp(87)); +// console.log(calculator.log(48763)); + +module.exports = { + Calculator +}; \ No newline at end of file diff --git a/lab3/main_test.js b/lab3/main_test.js new file mode 100644 index 0000000..369b58a --- /dev/null +++ b/lab3/main_test.js @@ -0,0 +1,70 @@ +const {describe, it} = require('node:test'); +const assert = require('assert'); +const { Calculator } = require('./main'); + +describe('Calculator', () => { + describe('exp', () => { + const calculator = new Calculator(); + + it('should throw "unsupported operand type" for non-finite numbers', () => { + const invalidInputs = [NaN, Infinity, -Infinity, 'string', null, undefined]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.exp(input), Error('unsupported operand type')); + }); + }); + + it('should throw "overflow" for large numbers', () => { + const largeInputs = [1000, 10000, 100000]; + largeInputs.forEach(input => { + assert.throws(() => calculator.exp(input), Error('overflow')); + }); + }); + + it('should return correct exp value for valid inputs', () => { + const testCases = [ + { input: 0, expected: 1 }, + { input: 1, expected: Math.exp(1) }, + { input: -1, expected: Math.exp(-1) }, + ]; + testCases.forEach(({ input, expected }) => { + assert.strictEqual(calculator.exp(input), expected); + }); + }); + }); + + describe('log', () => { + const calculator = new Calculator(); + + it('should throw "unsupported operand type" for non-finite numbers', () => { + const invalidInputs = [NaN, Infinity, -Infinity, 'string', null, undefined]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.log(input), Error('unsupported operand type')); + }); + }); + + it('should throw "math domain error (1)" for zero', () => { + const invalidInputs = [0]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.log(input), Error('math domain error (1)')); + }); + }); + + it('should throw "math domain error (2)" for finite negative numbers', () => { + const invalidInputs = [-1, -5, -100]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.log(input), Error('math domain error (2)')); + }); + }); + + it('should return correct log value for valid inputs', () => { + const testCases = [ + { input: 1, expected: 0 }, + { input: Math.E, expected: 1 }, + { input: 10, expected: Math.log(10) }, + ]; + testCases.forEach(({ input, expected }) => { + assert.strictEqual(calculator.log(input), expected); + }); + }); + }); +}); \ No newline at end of file diff --git a/lab3/validate.sh b/lab3/validate.sh new file mode 100755 index 0000000..7a758fb --- /dev/null +++ b/lab3/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab3-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails" + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%" + exit 1 + else + echo "[V] Coverage is 100%" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file