Skip to content

Commit 6f9614e

Browse files
xhulia028Xhulia Jasimileon-liang
authored
Add tests (#103)
* add new avatar colors * generate random whiteboard names * remove rag from compose.yml * implement invite users, setup realtime service * fix useGetMe * readd collaboration top bar * rename dtos * on delete cascade user whiteboard access * spotlessApply * ui improvements * ui improvements * create custom cursor component with rendered logic * publish and subscribe sockets * add cursor array for all users * fix upper menu bug * setup kafka * simple publish and subscribe * working version * Access dialog, fix viewport (#81) * access dialog, fix viewport * lint and format client * tiny fix * format and lint --------- Co-authored-by: Xhulia Jasimi <xhulia.jasimi@siemens.com> * some tuning * remove commit interval * replace kafka with redis pub sub * remove userid from subscribe to whiteboard events * disable access for invitees * create deployment files * fix deployment files * add server tests * add tests for client and test workflow * format and lint * generate * format * WhiteboardResponse * remove dependency * dummy commit * format and lint * delete dialog * hide style bar text node * package lock * package lock * delete package-lock.json in root * regenerate package-lock * json --------- Co-authored-by: Xhulia Jasimi <xhulia.jasimi@siemens.com> Co-authored-by: Leon Liang <hello@leonliang.lu>
1 parent d9f7d83 commit 6f9614e

File tree

20 files changed

+10256
-6776
lines changed

20 files changed

+10256
-6776
lines changed

.github/workflows/client-tests.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Lint and Format Check
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'client/**'
7+
workflow_dispatch:
8+
9+
jobs:
10+
client-test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v2
16+
17+
- name: Set up Node.js
18+
uses: actions/setup-node@v3
19+
with:
20+
node-version: '18.18.0'
21+
22+
- name: Install dependencies
23+
run: npm install
24+
working-directory: client
25+
26+
- name: Run Client Jest tests
27+
run: npm run test
28+
working-directory: client
29+
30+
31+
32+
33+

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ It addresses the need for a flexible, collaborative space where users can visual
2424
- Teams collaborating on product ideas, diagrams, and planning
2525
- Individuals who want to sketch out concepts or ideas
2626

27+
2728
---
2829

2930
## 💡 Example Use Cases

client/__tests__/formatDateTest.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import formatDate from '../src/util/formatDate';
2+
3+
describe('formatDate', () => {
4+
test('formats date string correctly', () => {
5+
const input = '2023-07-16T14:30:00';
6+
const expected = 'July 16 at 2:30 PM';
7+
expect(formatDate(input)).toBe(expected);
8+
});
9+
10+
test('handles different times of day', () => {
11+
expect(formatDate('2023-07-16T09:15:00')).toBe('July 16 at 9:15 AM');
12+
expect(formatDate('2023-07-16T23:45:00')).toBe('July 16 at 11:45 PM');
13+
});
14+
15+
test('handles different dates', () => {
16+
expect(formatDate('2023-01-01T12:00:00')).toBe('January 1 at 12:00 PM');
17+
expect(formatDate('2023-12-31T00:00:00')).toBe('December 31 at 12:00 AM');
18+
});
19+
20+
test('handles invalid date strings', () => {
21+
expect(formatDate('invalid-date')).toBe('Invalid Date');
22+
});
23+
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import generateColorFromString, {
2+
getHashOfString,
3+
normalizeHash,
4+
} from "@/util/generateUserUniqueColor";
5+
6+
describe('Color Generation Functions', () => {
7+
describe('getHashOfString', () => {
8+
test('returns consistent hash for same input', () => {
9+
const input = 'test@example.com';
10+
const firstHash = getHashOfString(input);
11+
const secondHash = getHashOfString(input);
12+
expect(firstHash).toBe(secondHash);
13+
});
14+
15+
test('returns different hashes for different inputs', () => {
16+
const hash1 = getHashOfString('test1@example.com');
17+
const hash2 = getHashOfString('test2@example.com');
18+
expect(hash1).not.toBe(hash2);
19+
});
20+
21+
test('handles empty string', () => {
22+
const hash = getHashOfString('');
23+
expect(hash).toBe(0);
24+
});
25+
26+
test('returns positive numbers', () => {
27+
const inputs = ['test', 'example@mail.com', 'longeremail@domain.com'];
28+
inputs.forEach(input => {
29+
const hash = getHashOfString(input);
30+
expect(hash).toBeGreaterThanOrEqual(0);
31+
});
32+
});
33+
});
34+
35+
describe('normalizeHash', () => {
36+
test('returns value within specified range', () => {
37+
const hash = 12345;
38+
const min = 0;
39+
const max = 360;
40+
const normalized = normalizeHash(hash, min, max);
41+
expect(normalized).toBeGreaterThanOrEqual(min);
42+
expect(normalized).toBeLessThan(max);
43+
});
44+
45+
test('returns integer values', () => {
46+
const hash = 12345;
47+
const normalized = normalizeHash(hash, 0, 100);
48+
expect(Number.isInteger(normalized)).toBe(true);
49+
});
50+
51+
test('handles different ranges', () => {
52+
const testCases = [
53+
{ hash: 12345, min: 0, max: 360 },
54+
{ hash: 12345, min: 50, max: 75 },
55+
{ hash: 12345, min: 25, max: 60 }
56+
];
57+
58+
testCases.forEach(({ hash, min, max }) => {
59+
const normalized = normalizeHash(hash, min, max);
60+
expect(normalized).toBeGreaterThanOrEqual(min);
61+
expect(normalized).toBeLessThan(max);
62+
});
63+
});
64+
});
65+
66+
describe('generateColorFromString', () => {
67+
test('returns valid HSL color string', () => {
68+
const color = generateColorFromString('test@example.com');
69+
expect(color).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
70+
});
71+
72+
test('returns consistent colors for same input', () => {
73+
const input = 'test@example.com';
74+
const color1 = generateColorFromString(input);
75+
const color2 = generateColorFromString(input);
76+
expect(color1).toBe(color2);
77+
});
78+
79+
test('returns different colors for different inputs', () => {
80+
const color1 = generateColorFromString('test1@example.com');
81+
const color2 = generateColorFromString('test2@example.com');
82+
expect(color1).not.toBe(color2);
83+
});
84+
85+
test('generates color with correct HSL ranges', () => {
86+
const color = generateColorFromString('test@example.com');
87+
const matches = color.match(/^hsl\((\d+), (\d+)%, (\d+)%\)$/);
88+
89+
expect(matches).not.toBeNull();
90+
if (matches) {
91+
const [, hue, saturation, lightness] = matches.map(Number);
92+
93+
expect(hue).toBeGreaterThanOrEqual(0);
94+
expect(hue).toBeLessThan(360);
95+
96+
expect(saturation).toBeGreaterThanOrEqual(50);
97+
expect(saturation).toBeLessThan(75);
98+
99+
expect(lightness).toBeGreaterThanOrEqual(25);
100+
expect(lightness).toBeLessThan(60);
101+
}
102+
});
103+
104+
test('handles special characters', () => {
105+
const specialChars = '!@#$%^&*()_+';
106+
expect(() => generateColorFromString(specialChars)).not.toThrow();
107+
expect(generateColorFromString(specialChars)).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
108+
});
109+
110+
test('handles empty string', () => {
111+
expect(() => generateColorFromString('')).not.toThrow();
112+
expect(generateColorFromString('')).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
113+
});
114+
});
115+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import generateWhiteboardName, { nameGenerators } from "@/util/generateWhiteboardName";
2+
3+
describe('generateWhiteboardName', () => {
4+
// Mock Math.random for deterministic testing
5+
let mockMathRandom: jest.SpyInstance;
6+
7+
beforeEach(() => {
8+
mockMathRandom = jest.spyOn(Math, 'random');
9+
});
10+
11+
afterEach(() => {
12+
mockMathRandom.mockRestore();
13+
});
14+
15+
test('generates valid name format', () => {
16+
const name = generateWhiteboardName();
17+
expect(name).toMatch(/^[A-Z][a-zA-Z]+ [A-Z][a-zA-Z]+$/);
18+
});
19+
20+
test('generates name from project category', () => {
21+
mockMathRandom
22+
.mockReturnValueOnce(0) // For category selection
23+
.mockReturnValueOnce(0) // For adjective selection
24+
.mockReturnValueOnce(0); // For noun selection
25+
26+
const name = generateWhiteboardName();
27+
expect(name).toBe('Active Project');
28+
});
29+
30+
test('generates name from creative category', () => {
31+
mockMathRandom
32+
.mockReturnValueOnce(0.25) // For category selection
33+
.mockReturnValueOnce(0) // For adjective selection
34+
.mockReturnValueOnce(0); // For noun selection
35+
36+
const name = generateWhiteboardName();
37+
expect(name).toBe('Creative Canvas');
38+
});
39+
40+
test('generates name from tech category', () => {
41+
mockMathRandom
42+
.mockReturnValueOnce(0.5) // For category selection
43+
.mockReturnValueOnce(0) // For adjective selection
44+
.mockReturnValueOnce(0); // For noun selection
45+
46+
const name = generateWhiteboardName();
47+
expect(name).toBe('Digital Hub');
48+
});
49+
50+
test('generates name from nature category', () => {
51+
mockMathRandom
52+
.mockReturnValueOnce(0.75) // For category selection
53+
.mockReturnValueOnce(0) // For adjective selection
54+
.mockReturnValueOnce(0); // For noun selection
55+
56+
const name = generateWhiteboardName();
57+
expect(name).toBe('Green Garden');
58+
});
59+
60+
test('generates different names on subsequent calls', () => {
61+
mockMathRandom.mockRestore();
62+
63+
const names = new Set();
64+
for (let i = 0; i < 10; i++) {
65+
names.add(generateWhiteboardName());
66+
}
67+
expect(names.size).toBeGreaterThan(1);
68+
});
69+
70+
test('all generated names use words from nameGenerators', () => {
71+
const name = generateWhiteboardName();
72+
const [adjective, noun] = name.split(' ');
73+
74+
// Create sets of all possible adjectives and nouns
75+
const allAdjectives = new Set(
76+
Object.values(nameGenerators).flatMap(category => category.adjectives)
77+
);
78+
const allNouns = new Set(
79+
Object.values(nameGenerators).flatMap(category => category.nouns)
80+
);
81+
82+
expect(allAdjectives).toContain(adjective);
83+
expect(allNouns).toContain(noun);
84+
});
85+
86+
test('handles all possible combinations in each category', () => {
87+
Object.entries(nameGenerators).forEach(([category, { adjectives, nouns }]) => {
88+
adjectives.forEach((adj, adjIndex) => {
89+
nouns.forEach((noun, nounIndex) => {
90+
mockMathRandom
91+
.mockReset()
92+
.mockReturnValueOnce(Object.keys(nameGenerators).indexOf(category) / Object.keys(nameGenerators).length)
93+
.mockReturnValueOnce(adjIndex / adjectives.length)
94+
.mockReturnValueOnce(nounIndex / nouns.length);
95+
96+
const name = generateWhiteboardName();
97+
expect(name).toBe(`${adj} ${noun}`);
98+
});
99+
});
100+
});
101+
});
102+
103+
test('name parts are properly capitalized', () => {
104+
for (let i = 0; i < 10; i++) {
105+
const name = generateWhiteboardName();
106+
const [adjective, noun] = name.split(' ');
107+
108+
expect(adjective[0]).toMatch(/[A-Z]/);
109+
expect(noun[0]).toMatch(/[A-Z]/);
110+
}
111+
});
112+
113+
test('generated names have exactly two words', () => {
114+
for (let i = 0; i < 10; i++) {
115+
const name = generateWhiteboardName();
116+
expect(name.split(' ')).toHaveLength(2);
117+
}
118+
});
119+
});

0 commit comments

Comments
 (0)