Skip to content

Commit

Permalink
Implemented initial unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mlhaufe committed Oct 28, 2024
1 parent ba7f4ba commit cb6693d
Show file tree
Hide file tree
Showing 18 changed files with 384 additions and 13 deletions.
4 changes: 3 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"EditorConfig.EditorConfig",
"github.vscode-github-actions",
"AlexShen.classdiagram-ts",
"ms-azuretools.vscode-bicep"
"ms-azuretools.vscode-bicep",
"vitest.explorer",
"kingwl.vscode-vitest-runner"
]
}
}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/azure-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ jobs:
run: npm run build
- name: Generate PWA Assets
run: npm run generate-pwa-assets
# - name: Run Unit Tests
# run: npm run test
- name: Run Unit Tests
run: npm run test
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ jobs:
run: npm run build
- name: Generate PWA Assets
run: npm run generate-pwa-assets
# - name: Run Unit Tests
# run: npm run test
- name: Run Unit Tests
run: npm run test
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"git.pruneOnFetch": true,
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.test.ts": "${capture}.ts",
"*.ts": "${capture}.test.ts",
"*.vue": "${capture}.test.ts"
}
}
File renamed without changes.
56 changes: 56 additions & 0 deletions server/utils/dedent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, it, expect } from 'vitest';
import dedent from './dedent';

describe('dedent', () => {
it('should remove leading spaces from each line', () => {
const input = `
line1
line2
line3
`;
const expected = `
line1
line2
line3
`;
expect(dedent(input)).toBe(expected);
});

it('should handle tabs correctly', () => {
const input = `
\t\tline1
\t\tline2
\t\tline3
\t\t`;
const expected = `
line1
line2
line3
`;
expect(dedent(input)).toBe(expected);
});

it('should return the same string if no indentation is found', () => {
const input = `line1\nline2\nline3`;
expect(dedent(input)).toBe(input);
});

it('should handle empty strings', () => {
const input = ``;
expect(dedent(input)).toBe(input);
});

it('should handle strings with inconsistent indentation', () => {
const input = `
line1
line2
line3
`;
const expected = `
line1
line2
line3
`;
expect(dedent(input)).toBe(expected);
});
});
1 change: 1 addition & 0 deletions server/utils/dedent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default (str: string) => {
if (!match)
return str

// Find the smallest indentation
const indent = Math.min(...match.map(x => x.length))
const re = new RegExp(`^[ \\t]{${indent}}`, 'gm')

Expand Down
46 changes: 46 additions & 0 deletions server/utils/groupBy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest';
import { groupBy } from './groupBy';

describe('groupBy', () => {
it('should group items by a string key', () => {
const items = [
{ category: 'fruit', name: 'apple' },
{ category: 'fruit', name: 'banana' },
{ category: 'vegetable', name: 'carrot' }
];
const result = groupBy(items, item => item.category);
expect(result).toEqual({
fruit: [
{ category: 'fruit', name: 'apple' },
{ category: 'fruit', name: 'banana' }
],
vegetable: [
{ category: 'vegetable', name: 'carrot' }
]
});
});

it('should group items by a numeric key', () => {
const items = [1.1, 2.2, 1.3, 2.4];
const result = groupBy(items, item => Math.floor(item));
expect(result).toEqual({
1: [1.1, 1.3],
2: [2.2, 2.4]
});
});

it('should handle an empty array', () => {
const items: any[] = [];
const result = groupBy(items, item => item);
expect(result).toEqual({});
});

it('should handle grouping by index', () => {
const items = ['a', 'b', 'c', 'd'];
const result = groupBy(items, (item, index) => index % 2);
expect(result).toEqual({
0: ['a', 'c'],
1: ['b', 'd']
});
});
});
37 changes: 37 additions & 0 deletions utils/camelCaseToSlug.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest';
import camelCaseToSlug from './camelCaseToSlug';

describe('camelCaseToSlug', () => {
it('should convert single camelCase word to slug', () => {
expect(camelCaseToSlug('camelCase')).toBe('camel-case');
});

it('should convert multiple camelCase words to slug', () => {
expect(camelCaseToSlug('thisIsATest')).toBe('this-is-a-test');
});

it('should handle strings with no camelCase', () => {
expect(camelCaseToSlug('test')).toBe('test');
});

it('should handle empty strings', () => {
expect(camelCaseToSlug('')).toBe('');
});

it('should handle strings with multiple uppercase letters', () => {
expect(camelCaseToSlug('camelCaseToSlug')).toBe('camel-case-to-slug');
});

it('should handle strings with numbers', () => {
expect(camelCaseToSlug('test123Case')).toBe('test123-case');
});

it('should handle strings with special characters by stripping them out', () => {
expect(camelCaseToSlug('test!@#$%^&*()_+Case')).toBe('test-case');
});

it('should trim leading and trailing whitespace', () => {
expect(camelCaseToSlug(' test ')).toBe('test');
});
});

10 changes: 8 additions & 2 deletions utils/camelCaseToSlug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
* @example
* camelCaseToSlug('camelCase') // 'camel-case'
*/
export default (str: string) =>
str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

export default (input: string): string =>
input.trim() // Remove leading and trailing whitespace
.replace(/[^a-zA-Z0-9]+/g, '-') // Replace non-alphanumeric characters with hyphens
.replace(/([a-z][^A-Z]*?)([A-Z])/g, '$1-$2') // Insert hyphen between lowercase and uppercase letters
.replace(/([A-Z])([A-Z][^A-Z]*?)/g, '$1-$2') // Insert hyphen between two uppercase letters
.replace(/-+/g, '-') // replace multiple hyphens with a single hyphen
.toLowerCase(); // Convert the entire string to lowercase
29 changes: 29 additions & 0 deletions utils/camelCaseToTitle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, expect } from 'vitest';
import camelCaseToTitle from './camelCaseToTitle';

describe('camelCaseToTitle', () => {
it('should convert camelCase to Title Case', () => {
expect(camelCaseToTitle('camelCaseString')).toBe('Camel Case String');
});

it('should handle single word', () => {
expect(camelCaseToTitle('word')).toBe('Word');
});

it('should handle empty string', () => {
expect(camelCaseToTitle('')).toBe('');
});

it('should handle multiple camelCase words', () => {
expect(camelCaseToTitle('thisIsATestString')).toBe('This Is A Test String');
});

it('should handle strings with numbers', () => {
expect(camelCaseToTitle('testString123')).toBe('Test String123');
});

// trim leading and trailing whitespace
it('should trim leading and trailing whitespace', () => {
expect(camelCaseToTitle(' test ')).toBe('Test');
});
});
5 changes: 3 additions & 2 deletions utils/camelCaseToTitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
* camelCaseToTitle('camelCaseString'); // 'Camel Case String'
*/
export default (str: string) =>
str.replace(/([a-z\d])([A-Z])/g, '$1 $2')
.replace(/\b\w/g, char => char.toUpperCase());
str.trim() // Remove leading and trailing whitespace
.replace(/([A-Z])/g, ' $1') // Add space before uppercase letters
.replace(/^./, str => str.toUpperCase()) // Capitalize the first letter
24 changes: 24 additions & 0 deletions utils/deSlugify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest';
import deSlugify from './deSlugify';

describe('deSlugify', () => {
it('should replace hyphens with spaces', () => {
expect(deSlugify('hello-world')).toBe('Hello World');
});

it('should convert to Title Case', () => {
expect(deSlugify('hello-world')).toBe('Hello World');
});

it('should handle multiple hyphens', () => {
expect(deSlugify('this-is-a-test')).toBe('This Is A Test');
});

it('should handle single word', () => {
expect(deSlugify('test')).toBe('Test');
});

it('should handle empty string', () => {
expect(deSlugify('')).toBe('');
});
});
71 changes: 71 additions & 0 deletions utils/debounce.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
import debounce from './debounce';

describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.clearAllTimers();
});

it('should call the function after the specified delay', () => {
// Arrange
const func = vi.fn();
const debouncedFunc = debounce(func, 200);

// Act
debouncedFunc();

// Assert
expect(func).not.toHaveBeenCalled();
vi.advanceTimersByTime(200);
expect(func).toHaveBeenCalled();
});

it('should not call the function if called again within the delay', () => {
// Arrange
const func = vi.fn();
const debouncedFunc = debounce(func, 200);

// Act
debouncedFunc();
debouncedFunc();

// Assert
expect(func).not.toHaveBeenCalled();
vi.advanceTimersByTime(200);
expect(func).toHaveBeenCalledTimes(1);
});

it('should call the function with the correct arguments', () => {
// Arrange
const func = vi.fn();
const debouncedFunc = debounce(func, 200);

// Act
debouncedFunc('arg1', 'arg2');

// Assert
vi.advanceTimersByTime(200);
expect(func).toHaveBeenCalledWith('arg1', 'arg2');
});

it('should reset the delay if called again within the delay period', () => {
// Arrange
const func = vi.fn();
const debouncedFunc = debounce(func, 200);

// Act
debouncedFunc();
vi.advanceTimersByTime(100);
debouncedFunc();
vi.advanceTimersByTime(100);

// Assert
expect(func).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(func).toHaveBeenCalled();
});
});
4 changes: 1 addition & 3 deletions utils/debounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export default function (func: Function, delay: number) {
return function (...args: any[]) {
if (timeoutId)
clearTimeout(timeoutId);
timeoutId = self.setTimeout(() => {
func(...args);
}, delay);
timeoutId = setTimeout(() => { func(...args); }, delay);

Check failure on line 10 in utils/debounce.ts

View workflow job for this annotation

GitHub Actions / validate

Type 'Timeout' is not assignable to type 'number'.
};
}
Loading

0 comments on commit cb6693d

Please sign in to comment.