chore: lint-format-docs-tests-bugfixes (#105)

* docs: add jsdoc

* feat: change enums to as const objects

* chore(test): add themeColors.test.ts

* fix: fix tests and bugs with strings.ts util

* fix: path alias imports and tsconfig file bug

* fix: remove --max-warnings 0
This commit is contained in:
doprz
2024-02-22 22:42:58 -06:00
parent 8ab60c9f01
commit 8a6e9070e0
134 changed files with 986 additions and 623 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
export default interface BrowserActionMessages {
/** make it so that clicking the browser action will open the popup.html */
enableBrowserAction: () => void;

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
export default interface HotReloadingMessages {
reloadExtension: () => void;
}

View File

@@ -1,5 +1,8 @@
import { Course } from '../types/Course';
import type { Course } from '@shared/types/Course';
/**
* Represents a collection of user schedule messages.
*/
export interface UserScheduleMessages {
/**
* Add a course to a schedule

View File

@@ -1,8 +1,9 @@
import { createMessenger } from 'chrome-extension-toolkit';
import BrowserActionMessages from './BrowserActionMessages';
import TabManagementMessages from './TabManagementMessages';
import TAB_MESSAGES from './TabMessages';
import { UserScheduleMessages } from './UserScheduleMessages';
import type BrowserActionMessages from './BrowserActionMessages';
import type TabManagementMessages from './TabManagementMessages';
import type TAB_MESSAGES from './TabMessages';
import type { UserScheduleMessages } from './UserScheduleMessages';
/**
* This is a type with all the message definitions that can be sent TO the background script

View File

@@ -15,6 +15,4 @@ export const ExtensionStore = createLocalStore<IExtensionStore>({
lastUpdate: Date.now(),
});
debugStore({ ExtensionStore });

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-classes-per-file */
import { Serialized } from 'chrome-extension-toolkit';
import { CourseMeeting } from './CourseMeeting';
import type { Serialized } from 'chrome-extension-toolkit';
import type { CourseMeeting } from './CourseMeeting';
import { CourseSchedule } from './CourseSchedule';
import Instructor from './Instructor';
@@ -12,12 +12,17 @@ export type InstructionMode = 'Online' | 'In Person' | 'Hybrid';
/**
* The status of a course (e.g. open, closed, waitlisted, cancelled)
*/
export enum Status {
OPEN = 'OPEN',
CLOSED = 'CLOSED',
WAITLISTED = 'WAITLISTED',
CANCELLED = 'CANCELLED',
}
export const Status = {
OPEN: 'OPEN',
CLOSED: 'CLOSED',
WAITLISTED: 'WAITLISTED',
CANCELLED: 'CANCELLED',
} as const;
/**
* Represents the type of status for a course.
*/
export type StatusType = (typeof Status)[keyof typeof Status];
/**
* Represents a semester, with the year and the season for when a course is offered
@@ -49,7 +54,7 @@ export class Course {
/** The number of credits that a course is worth */
creditHours: number;
/** Is the course open, closed, waitlisted, or cancelled? */
status: Status;
status: StatusType;
/** all the people that are teaching this course, and some metadata about their names */
instructors: Instructor[];
/** Some courses at UT are reserved for certain groups of people or people within a certain major, which makes it difficult for people outside of that group to register for the course. */

View File

@@ -1,4 +1,4 @@
import { Serialized } from 'chrome-extension-toolkit';
import type { Serialized } from 'chrome-extension-toolkit';
/**
* a map of the days of the week that a class is taught, and the corresponding abbreviation

View File

@@ -1,5 +1,7 @@
import { Serialized } from 'chrome-extension-toolkit';
import { CourseMeeting, Day, DAY_MAP } from './CourseMeeting';
import type { Serialized } from 'chrome-extension-toolkit';
import type { Day } from './CourseMeeting';
import { CourseMeeting, DAY_MAP } from './CourseMeeting';
/**
* This represents the schedule for a course, which includes all the meeting times for the course, as well as helper functions for parsing, serializing, and deserializing the schedule

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-classes-per-file */
import { PointOptionsObject } from 'highcharts';
import { Semester } from './Course';
/**
* Each of the possible letter grades that can be given in a course

View File

@@ -1,4 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit';
import type { Serialized } from 'chrome-extension-toolkit';
import { capitalize } from '../util/string';
/**

View File

@@ -1,4 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit';
import type { Serialized } from 'chrome-extension-toolkit';
import { Course } from './Course';
/**

View File

@@ -1,11 +1,19 @@
import { theme } from 'unocss/preset-mini';
/**
* Represents the colors for a course.
*/
export interface CourseColors {
primaryColor: string;
secondaryColor: string;
}
// calculates luminance of a hex string
/**
* Calculates the luminance of a given hexadecimal color.
*
* @param hex - The hexadecimal color value.
* @returns The luminance value between 0 and 1.
*/
export function getLuminance(hex: string): number {
let r = parseInt(hex.substring(1, 3), 16);
let g = parseInt(hex.substring(3, 5), 16);

View File

@@ -1,15 +1,18 @@
import React, { SVGProps } from 'react';
import type { StatusType } from '@shared/types/Course';
import { Status } from '@shared/types/Course';
import type { SVGProps } from 'react';
import React from 'react';
import ClosedIcon from '~icons/material-symbols/lock';
import WaitlistIcon from '~icons/material-symbols/timelapse';
import CancelledIcon from '~icons/material-symbols/warning';
import { Status } from '../types/Course';
/**
* Get Icon component based on status
* @param props.status status
* @returns React.ReactElement - the icon component
*/
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: Status }): React.ReactElement {
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): React.ReactElement {
const { status, ...rest } = props;
switch (props.status) {

View File

@@ -18,6 +18,8 @@ export function capitalize(input: string): string {
}
capitalized += ' ';
}
capitalized = capitalized.trim(); // Remove extra space
return capitalized;
}
@@ -31,7 +33,7 @@ export function capitalizeFirstLetter(input: string): string {
}
/**
* Cuts the
* Cuts the input string to the specified length and adds an ellipsis if the string is longer than the specified length.
* @param input The string to ellipsify.
* @param length The length of the string to return.
* @returns The ellipsified string.

View File

@@ -1,17 +1,17 @@
import { describe, expect, it } from 'vitest';
import { capitalize } from '../string';
import { capitalize, capitalizeFirstLetter, ellipsify } from '../string';
// TODO: Fix `string.ts` and `string.test.ts` to make the tests pass
// `capitalize` is adding an extra space at the end of the word.
describe('capitalize', () => {
it('should capitalize the first letter of each word', () => {
// Debug
const word = 'hello world';
const capitalized = capitalize(word);
console.log(capitalize(word));
console.log(capitalized.length);
console.log(capitalized.split(''));
// const word = 'hello world';
// const capitalized = capitalize(word);
// console.log(capitalize(word));
// console.log(capitalized.length);
// console.log(capitalized.split(''));
// Test case 1: Single word
expect(capitalize('hello')).toBe('Hello');
@@ -25,15 +25,40 @@ describe('capitalize', () => {
// Test case 4: Words with hyphens and spaces
expect(capitalize('hello-world test')).toBe('Hello-World Test');
});
});
it('should not change the capitalization of the remaining letters', () => {
// Test case 1: All lowercase
expect(capitalize('hello')).toBe('Hello');
describe('capitalizeFirstLetter', () => {
it('should return a string with the first letter capitalized', () => {
// Test case 1: Single word
expect(capitalizeFirstLetter('hello')).toBe('Hello');
// Test case 2: All uppercase
expect(capitalize('WORLD')).toBe('WORLD');
// Test case 2: Word with all lowercase letters
expect(capitalizeFirstLetter('world')).toBe('World');
// Test case 3: Mixed case
expect(capitalize('HeLLo WoRLd')).toBe('Hello World');
// Test case 3: Word with all uppercase letters
expect(capitalizeFirstLetter('EXAMPLE')).toBe('Example');
// Test case 4: Word with mixed case letters
expect(capitalizeFirstLetter('tEsT')).toBe('Test');
});
it('should handle empty string input', () => {
expect(capitalizeFirstLetter('')).toBe('');
});
});
describe('ellipsify', () => {
it('should add ellipsis if the input string exceeds the specified character limit', () => {
// Test case 1: Input string is shorter than the character limit
expect(ellipsify('Hello', 10)).toBe('Hello');
// Test case 2: Input string is equal to the character limit
expect(ellipsify('Hello World', 11)).toBe('Hello World');
// Test case 3: Input string is longer than the character limit
expect(ellipsify('Hello World', 5)).toBe('Hello...');
// Test case 4: Input string is empty
expect(ellipsify('', 5)).toBe('');
});
});

View File

@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import { getThemeColorHexByName, getThemeColorRgbByName, hexToRgb } from '../themeColors';
describe('hexToRgb', () => {
it('should convert hex color to RGB', () => {
expect(hexToRgb('#BF5700')).toEqual([191, 87, 0]);
expect(hexToRgb('#333F48')).toEqual([51, 63, 72]);
expect(hexToRgb('#f8971f')).toEqual([248, 151, 31]);
expect(hexToRgb('#ffd600')).toEqual([255, 214, 0]);
expect(hexToRgb('#a6cd57')).toEqual([166, 205, 87]);
expect(hexToRgb('#579d42')).toEqual([87, 157, 66]);
expect(hexToRgb('#00a9b7')).toEqual([0, 169, 183]);
expect(hexToRgb('#005f86')).toEqual([0, 95, 134]);
expect(hexToRgb('#9cadb7')).toEqual([156, 173, 183]);
expect(hexToRgb('#d6d2c4')).toEqual([214, 210, 196]);
expect(hexToRgb('#95a5a6')).toEqual([149, 165, 166]);
expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]);
expect(hexToRgb('#af2e2d')).toEqual([175, 46, 45]);
expect(hexToRgb('#1a2024')).toEqual([26, 32, 36]);
expect(hexToRgb('#22c55e')).toEqual([34, 197, 94]);
expect(hexToRgb('#a3e635')).toEqual([163, 230, 53]);
expect(hexToRgb('#84CC16')).toEqual([132, 204, 22]);
expect(hexToRgb('#FDE047')).toEqual([253, 224, 71]);
expect(hexToRgb('#FACC15')).toEqual([250, 204, 21]);
expect(hexToRgb('#F59E0B')).toEqual([245, 158, 11]);
expect(hexToRgb('#FB923C')).toEqual([251, 146, 60]);
expect(hexToRgb('#F97316')).toEqual([249, 115, 22]);
expect(hexToRgb('#EA580C')).toEqual([234, 88, 12]);
expect(hexToRgb('#DC2626')).toEqual([220, 38, 38]);
expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]);
});
});
describe('getThemeColorHexByName', () => {
it('should return the hex color value by name', () => {
expect(getThemeColorHexByName('ut-burntorange')).toEqual('#BF5700');
expect(getThemeColorHexByName('ut-offwhite')).toEqual('#D6D2C4');
expect(getThemeColorHexByName('ut-black')).toEqual('#333F48');
// Add more test cases for other theme color names
});
});
describe('getThemeColorRgbByName', () => {
it('should return the RGB color value by name', () => {
expect(getThemeColorRgbByName('ut-burntorange')).toEqual([191, 87, 0]);
expect(getThemeColorRgbByName('ut-offwhite')).toEqual([214, 210, 196]);
expect(getThemeColorRgbByName('ut-black')).toEqual([51, 63, 72]);
// Add more test cases for other theme color names
});
});

View File

@@ -2,24 +2,24 @@ export const colors = {
ut: {
burntorange: '#BF5700',
black: '#333F48',
orange: '#f8971f',
yellow: '#ffd600',
lightgreen: '#a6cd57',
green: '#579d42',
teal: '#00a9b7',
blue: '#005f86',
gray: '#9cadb7',
offwhite: '#d6d2c4',
concrete: '#95a5a6',
red: '#B91C1C' // Not sure if this should be here, but it's used for remove course, and add course is ut-green
orange: '#F8971F',
yellow: '#FFD600',
lightgreen: '#A6CD57',
green: '#579D42',
teal: '#00A9B7',
blue: '#005F86',
gray: '#9CADB7',
offwhite: '#D6D2C4',
concrete: '#95A5A6',
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
},
theme: {
red: '#af2e2d',
black: '#1a2024',
red: '#AF2E2D',
black: '#1A2024',
},
gradeDistribution: {
a: '#22c55e',
aminus: '#a3e635',
a: '#22C55E',
aminus: '#A3E635',
bplus: '#84CC16',
b: '#FDE047',
bminus: '#FACC15',
@@ -31,7 +31,7 @@ export const colors = {
dminus: '#B91C1C',
f: '#B91C1C',
},
} as const;
} as const satisfies Record<string, Record<string, string>>;
type NestedKeys<T> = {
[K in keyof T]: T[K] extends Record<string, any> ? `${string & K}-${string & keyof T[K]}` : never;
@@ -42,6 +42,10 @@ type NestedKeys<T> = {
*/
export type ThemeColor = NestedKeys<typeof colors>;
/**
* Flattened colors object.
* @type {Record<ThemeColor, string>}
*/
export const colorsFlattened = Object.entries(colors).reduce(
(acc, [prefix, group]) => {
for (const [name, hex] of Object.entries(group)) {
@@ -52,9 +56,18 @@ export const colorsFlattened = Object.entries(colors).reduce(
{} as Record<ThemeColor, string>
);
const hexToRgb = (hex: string) =>
/**
* Converts a hexadecimal color code to an RGB color array.
* @param hex The hexadecimal color code to convert.
* @returns An array representing the RGB color values.
*/
export const hexToRgb = (hex: string) =>
hex.match(/[0-9a-f]{2}/gi).map(partialHex => parseInt(partialHex, 16)) as [number, number, number];
/**
* Represents the flattened RGB values of the colors.
* @type {Record<ThemeColor, ReturnType<typeof hexToRgb>>}
*/
const colorsFlattenedRgb = Object.fromEntries(
Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)])
) as Record<ThemeColor, ReturnType<typeof hexToRgb>>;

View File

@@ -5,7 +5,9 @@ export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
/**
*
* Pauses the execution for the specified number of milliseconds.
* @param milliseconds - The number of milliseconds to sleep.
* @returns A promise that resolves after the specified number of milliseconds.
*/
export const sleep = (milliseconds: number): Promise<void> => new Promise(resolve => setTimeout(resolve, milliseconds));