feat: unit tests for utils (#92)

* init

* get unit test working for course.ts file

* added some other tests

* tests halfway + change config to accomodate js files

* utility functions tests done

* fix: config file + miscellaneous fixes

* fix: prepare script

* fix lock file

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Toyin Muhammed
2023-12-20 14:28:40 +03:00
committed by GitHub
co-authored by Rotimi Best
parent 4b2e58b1de
commit 6d689ede45
24 changed files with 2293 additions and 35 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
};
+21
View File
@@ -0,0 +1,21 @@
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from 'jest';
const config: Config = {};
export default config;
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.svelte$': 'svelte-jester',
'^.+\\.ts$': 'ts-jest',
'\\.[jt]sx?$': 'babel-jest'
}
};
+12 -1
View File
@@ -13,7 +13,9 @@
"export": "vite build --entry \"/ /404 login signup forgot onboarding courses org lms course\"",
"postexport": "mv __sapper__/export/404/index.html __sapper__/export/404.html",
"start": "node build",
"prepare": "svelte-kit sync"
"prepare": "svelte-kit sync",
"test": "jest",
"test:watch": "pnpm run test --watch"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.386.0",
@@ -58,16 +60,22 @@
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"@babel/preset-typescript": "^7.23.3",
"@babel/runtime": "^7.22.6",
"@sveltejs/kit": "^1.29.0",
"@tailwindcss/typography": "^0.5.9",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/svelte": "^4.0.5",
"@types/jest": "^29.5.10",
"@types/lodash": "^4.14.196",
"@types/nodemailer": "^6.4.11",
"autoprefixer": "^10.4.14",
"babel-jest": "^29.7.0",
"carbon-components-svelte": "^0.79.0",
"carbon-icons-svelte": "^12.1.0",
"dotenv": "^16.3.1",
"eslint-config-custom": "workspace:*",
"jest": "^29.7.0",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"sass": "^1.64.2",
@@ -75,9 +83,12 @@
"svelte": "^4.1.2",
"svelte-calendar": "^3.1.6",
"svelte-dnd-action": "^0.9.24",
"svelte-jester": "^3.0.0",
"svelte-loading-spinners": "^0.3.4",
"svelte-preprocess": "^5.0.4",
"tailwindcss": "^3.3.2",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"tsconfig": "workspace:*",
"tslib": "^2.6.1",
"typescript": "^5.1.6",
@@ -1,4 +1,5 @@
<script lang="ts">
import { browser } from '$app/environment';
import Modal from '$lib/components/Modal/index.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
@@ -98,7 +99,7 @@
}, 500);
}
$: note = getTextFromHTML($lesson?.materials?.note || '');
$: note = browser ? getTextFromHTML($lesson?.materials?.note || '') : '';
</script>
<Modal
@@ -1,4 +1,5 @@
<script lang="ts">
import { browser } from '$app/environment';
import isEmpty from 'lodash/isEmpty';
import { useCompletion } from 'ai/svelte';
import MODES from '$lib/utils/constants/mode.js';
@@ -84,7 +85,7 @@
if (!document) return false;
return getTextFromHTML(note) === '';
return browser ? getTextFromHTML(note) === '' : false;
}
function isMaterialsEmpty(materials: LessonPage['materials']) {
@@ -0,0 +1,30 @@
import isSubmissionEarly from './isSubmissionEarly';
describe('isSubmissionEarly', () => {
test('should return true when createdAt is earlier than dueDate', () => {
const dueDate = Date.now();
const createdAt = new Date('2023-12-15');
const result = isSubmissionEarly(createdAt, dueDate);
expect(result).toBe(true);
});
test('should return true when both createdAt and dueDate are null', () => {
const dueDate = null;
const createdAt = null;
const result = isSubmissionEarly(createdAt, dueDate);
expect(result).toBe(true);
});
test('should return true when createdAt is the same as dueDate', () => {
const dueDate = new Date('2023-12-15');
const createdAt = new Date('2023-12-15');
const result = isSubmissionEarly(createdAt, dueDate);
expect(result).toBe(true);
});
test('should return false when createdAt is later than dueDate', () => {
const dueDate = new Date('2023-12-15');
const createdAt = new Date('2023-12-16');
const result = isSubmissionEarly(createdAt, dueDate);
expect(result).toBe(false);
});
});
@@ -0,0 +1,18 @@
import { isCourseFree } from './course';
describe('course.ts', () => {
test('Should return True when cost is 0', () => {
const result = isCourseFree(0);
expect(result).toBeTruthy();
});
test('Should return true when cost is negative', () => {
const result = isCourseFree(-10);
expect(result).toBeTruthy();
});
test('Should return True when cost is Not A Number', () => {
const result = isCourseFree(NaN);
expect(result).toBeTruthy();
});
});
@@ -1,4 +1,3 @@
import { browser } from '$app/environment';
import type { Course } from '../types';
export const isCourseFree = (cost: number) => !(Number(cost) > 0);
@@ -19,8 +18,6 @@ export const getStudentInviteLink = (_course: Course, orgSiteName: string, origi
};
export const getTextFromHTML = (html: string): string => {
if (!browser) return html;
const dummyDiv = document.createElement('div');
dummyDiv.innerHTML = html;
@@ -0,0 +1,36 @@
import { calDateDiff, getGreeting } from './date';
describe('calDateDiff', () => {
test('should return the correct time difference when the input date is in the past', () => {
const inputDate = new Date('2022-01-01');
const expectedOutput = '2 years ago';
const result = calDateDiff(inputDate);
expect(result).toBe(expectedOutput);
});
test('should return the correct time difference when the input date is a number', () => {
const inputDate = new Date(1640995200000); // January 1, 2022
const expectedOutput = '2 years ago';
const result = calDateDiff(inputDate);
expect(result).toBe(expectedOutput);
});
});
describe('getGreeting', () => {
test("should return 'Good Morning' when the current time is before 12pm", () => {
jest.useFakeTimers().setSystemTime(new Date('2023-12-10T00:21:01.691Z'));
expect(getGreeting()).toBe('Good Morning');
});
test("should return 'Good Afternoon' when the current time is after 12pm", () => {
jest.useFakeTimers().setSystemTime(new Date('2023-12-10T14:21:01.691Z'));
expect(getGreeting()).toBe('Good Afternoon');
});
test("should return 'Good Evening' when the current time is after 6pm", () => {
jest.useFakeTimers().setSystemTime(new Date('2023-12-10T19:21:01.691Z'));
expect(getGreeting()).toBe('Good Evening');
});
});
@@ -0,0 +1,46 @@
import { formatYoutubeVideo, getEmbedId } from './formatYoutubeVideo';
describe('format youtube video', () => {
test('should return the same url when given a valid youtube embed url', () => {
const url = 'https://www.youtube.com/embed/qajK1J1neAM';
const errors = { video: '' };
const result = formatYoutubeVideo(url, errors);
expect(result).toBe(url);
});
test('should return the correct embed url when given a youtube url with a start time parameter', () => {
const url = 'https://www.youtube.com/watch?v=qajK1J1neAM&start=123';
const errors = { video: '' };
const result = formatYoutubeVideo(url, errors);
expect(result).toBe('https://www.youtube.com/embed/qajK1J1neAM');
});
});
describe('getEmbedId', () => {
test('should return the correct embed id when the url contains "embed"', () => {
const url = 'https://www.youtube.com/embed/qajK1J1neAM';
const expectedEmbedId = 'qajK1J1neAM';
const result = getEmbedId(url);
expect(result).toBe(expectedEmbedId);
});
test('should return an empty string when the url does not contain "watch" or "embed" and is not in the format "youtu.be/"', () => {
const url = 'https://www.youtube.com/invalid-url';
const expectedEmbedId = '';
const result = getEmbedId(url);
expect(result).toBe(expectedEmbedId);
});
test('should return an empty string when the url is an empty string', () => {
const url = '';
const expectedEmbedId = '';
const result = getEmbedId(url);
expect(result).toBe(expectedEmbedId);
});
});
@@ -0,0 +1,25 @@
import genUniqueId from './genUniqueId';
describe('Generate Unique Ids', () => {
test('should start with letter', () => {
const result = genUniqueId();
const firstChar = result.charAt(0);
expect(firstChar).toMatch(/[a-zA-Z]/);
});
test('should not contain special characters', () => {
const result = genUniqueId();
expect(result).toMatch(/^[a-zA-Z0-9]+$/);
});
test('should return different ids on multiple calls', () => {
const result1 = genUniqueId();
const result2 = genUniqueId();
expect(result1).not.toEqual(result2);
});
test('multiple calls in closer time frames should have same first 3 letters', () => {
const result1 = genUniqueId();
const result2 = genUniqueId();
expect(result1.substring(0, 4)).toEqual(result2.substring(0, 4));
});
});
@@ -0,0 +1,17 @@
import generateSlug from './generateSlug';
describe('generateSlug', () => {
test('should return a string with lowercase letters and hyphens when given a valid title', () => {
const title = 'This is a Valid Title';
const expected = 'this-is-a-valid-title' + `-${new Date().getTime()}`;
const result = generateSlug(title);
expect(result).toBe(expected);
});
test('should include the current timestamp at the end of the string', () => {
const title = 'This is a Valid Title';
const result = generateSlug(title);
const timestamp = result.split('-').pop();
expect(Number(timestamp)).toBeGreaterThan(0);
});
});
@@ -0,0 +1,26 @@
import generateUUID from './generateUUID';
import { isUUID } from './isUUID';
// This test suite tests both the generateUUID and isUUID functions
describe('GenerateUUID', () => {
test('should be true when tested with the uuid function', () => {
const result = generateUUID();
expect(isUUID(result)).toBe(true);
});
test('should generate unique UUIDs each time', () => {
const uuid1 = generateUUID();
const uuid2 = generateUUID();
expect(uuid1).not.toBe(uuid2);
});
test('should return true for a valid UUID', () => {
const uuid = '123e4567-e89b-12d3-a456-426614174000';
expect(isUUID(uuid)).toBe(true);
});
test('should return false for an invalid UUID', () => {
const uuid = 'invalid-uuid';
expect(isUUID(uuid)).toBe(false);
});
});
@@ -0,0 +1,27 @@
import getCurrencyFormatter from './getCurrencyFormatter';
describe('GetCurrencyFormatter', () => {
test('should return a new instance of Intl.NumberFormat with the correct locale and currency options when currency is "NGN"', () => {
const result = getCurrencyFormatter('NGN');
expect(result).toBeInstanceOf(Intl.NumberFormat);
expect(result.resolvedOptions().locale).toBe('en-NG');
expect(result.resolvedOptions().currency).toBe('NGN');
expect(result.resolvedOptions().minimumFractionDigits).toBe(2);
});
test('should return a new instance of Intl.NumberFormat with the correct locale and currency options when currency is "USD"', () => {
const result = getCurrencyFormatter('USD');
expect(result).toBeInstanceOf(Intl.NumberFormat);
expect(result.resolvedOptions().locale).toBe('en-US');
expect(result.resolvedOptions().currency).toBe('USD');
expect(result.resolvedOptions().minimumFractionDigits).toBe(2);
});
test('should return a new instance of Intl.NumberFormat with the correct locale and currency options when currency is "EUR"', () => {
const result = getCurrencyFormatter('EUR');
expect(result).toBeInstanceOf(Intl.NumberFormat);
expect(result.resolvedOptions().locale).toBe('en-US');
expect(result.resolvedOptions().currency).toBe('EUR');
expect(result.resolvedOptions().minimumFractionDigits).toBe(2);
});
});
@@ -0,0 +1,23 @@
import { isObject } from './isObject';
describe('isObject', () => {
test('should return true for an empty object', () => {
const obj = {};
expect(isObject(obj)).toBe(true);
});
test('should return false for a non-object', () => {
const obj = 'not an object';
expect(isObject(obj)).toBe(false);
});
test('should return true for a nested object', () => {
const obj = { prop1: { prop2: 'value2' } };
expect(isObject(obj)).toBe(true);
});
test('should return true for an object with properties', () => {
const obj = { prop1: 'value1', prop2: 'value2' };
expect(isObject(obj)).toBe(true);
});
});
@@ -1,3 +1,4 @@
//
export function isObject(obj) {
return obj === Object(obj);
}
@@ -0,0 +1,60 @@
import { genQuizPin, generateSitename } from './org';
describe('Generate Quiz Pin', () => {
test('should return a random 6-digit pin', () => {
const result = genQuizPin();
expect(result.toString().length).toBe(6);
});
test('should return random pin each time', () => {
const result1 = genQuizPin();
const result2 = genQuizPin();
expect(result1).not.toBe(result2);
});
test('should generate a pin between 100000 and 999999', () => {
const result = genQuizPin();
expect(result).toBeGreaterThanOrEqual(100000);
expect(result).toBeLessThanOrEqual(999999);
});
test('should not generate a negative pin', () => {
const result = genQuizPin();
expect(result).toBeGreaterThanOrEqual(0);
});
test('should generate an integer pin', () => {
const result = genQuizPin();
expect(Number.isInteger(result)).toBe(true);
});
test('should not generate a zero pin', () => {
const result = genQuizPin();
expect(result).not.toBe(0);
});
});
describe('Generate Site name', () => {
test('should return slug-like string', () => {
const result = generateSitename('classroom org international');
const expected = 'classroom-org-international';
expect(result).toBe(expected);
});
test('should remove special characters', () => {
const result = generateSitename('classroom@ org! international');
const expected = 'classroom-org-international';
expect(result).toBe(expected);
});
test('should return empty string when orgName is an empty string', () => {
const result = generateSitename('');
const expected = '';
expect(result).toBe(expected);
});
test('should return slug-like string with all uppercase characters converted to lowercase', () => {
const result = generateSitename('Classroom Org International');
const expected = 'classroom-org-international';
expect(result).toBe(expected);
});
});
@@ -0,0 +1,38 @@
import { removeDuplicate } from './removeDuplicate';
describe('removeDuplicate', () => {
test('should remove duplicate elements of an array', () => {
const result = removeDuplicate([1, 1, 2, 3, 3, 4, 5]);
const expected = [1, 2, 3, 4, 5];
expect(result).toStrictEqual(expected);
expect(result.length).toBe(5);
});
test('should return an empty array', () => {
const result = removeDuplicate([]);
const expected = [];
expect(result).toStrictEqual(expected);
expect(result.length).toBe(0);
});
test('should return an array with the same element', () => {
const result = removeDuplicate([1]);
const expected = [1];
expect(result).toStrictEqual(expected);
expect(result.length).toBe(1);
});
test('should return an array with only one element', () => {
const result = removeDuplicate([1, 1, 1, 1]);
const expected = [1];
expect(result).toStrictEqual(expected);
expect(result.length).toBe(1);
});
test('should return an array with the unique elements in the same order as the original array', () => {
const result = removeDuplicate([1, 2, 3, 4, 5, 1, 2, 3, 4, 5]);
const expected = [1, 2, 3, 4, 5];
expect(result).toStrictEqual(expected);
expect(result.length).toBe(5);
});
});
@@ -0,0 +1,26 @@
import showAppsSideBar from './showAppsSideBar';
describe('ShowAppSideBar', () => {
test("should return true when path includes '/lessons/' and does not include 'landingpage'", () => {
const path = '/lessons/example';
const result = showAppsSideBar(path);
expect(result).toBe(true);
});
test("should return false when path does not include '/lessons/'", () => {
const path = '/home';
const result = showAppsSideBar(path);
expect(result).toBe(false);
});
test("should return false when path includes 'landingpage'", () => {
const path = '/lessons/landingpage';
const result = showAppsSideBar(path);
expect(result).toBe(false);
});
test('should return false when path is empty', () => {
const path = '';
const result = showAppsSideBar(path);
expect(result).toBe(false);
});
});
@@ -0,0 +1,28 @@
import { capitalizeFirstLetter } from './string';
describe('capitalize first letter', () => {
test('should return the same string with the first letter capitalized', () => {
const result = capitalizeFirstLetter('classroom');
expect(result).toEqual('Classroom');
});
test('should return an empty string when given an empty string', () => {
const result = capitalizeFirstLetter('');
expect(result).toEqual('');
});
test('should return the same string when given a string with all uppercase letters', () => {
const result = capitalizeFirstLetter('UPPERCASE');
expect(result).toEqual('UPPERCASE');
});
test('should return the same string when given a string with a non-alphabetical first character', () => {
const result = capitalizeFirstLetter('123abc');
expect(result).toEqual('123abc');
});
test('should return the first letter of the first word capitalized when given a sentence', () => {
const result = capitalizeFirstLetter('classroomio is an open source project');
expect(result).toEqual('Classroomio is an open source project');
});
});
@@ -0,0 +1,28 @@
import { validateEmail } from './validateEmail';
describe('validate email', () => {
test('should return true for a valid email format', () => {
const result = validateEmail('[email protected]');
expect(result).toBeTruthy();
});
test('should return false for an empty email', () => {
const result = validateEmail('');
expect(result).toBeFalsy();
});
test('should return true for a valid email with uppercase letters', () => {
const result = validateEmail('[email protected]');
expect(result).toBeTruthy();
});
test('should return false for an invalid email format', () => {
const result = validateEmail('test@example');
expect(result).toBeFalsy();
});
test('should return true for a valid email with special characters', () => {
const result = validateEmail('[email protected]');
expect(result).toBeTruthy();
});
});
+5 -1
View File
@@ -8,7 +8,11 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
"strict": true,
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
"module": "ESNext",
"moduleResolution": "node",
"noImplicitAny": false
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
+1816 -28
View File
File diff suppressed because it is too large Load Diff