mirror of
https://github.com/theoludwig/theoludwig.git
synced 2025-05-29 22:37:44 +02:00
chore: cleaner setup
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import config from "@repo/eslint-config"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
@ -1,29 +1,28 @@
|
||||
{
|
||||
"name": "@repo/utils",
|
||||
"version": "4.1.3",
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./constants": "./src/constants.ts",
|
||||
"./dates": "./src/dates.ts",
|
||||
"./strings": "./src/strings.ts"
|
||||
"./objects": "./src/objects.ts",
|
||||
"./strings": "./src/strings.ts",
|
||||
"./types": "./src/types.ts",
|
||||
"./urls": "./src/urls.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:eslint": "eslint src --max-warnings 0",
|
||||
"lint:typescript": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui --no-open"
|
||||
"test": "node --experimental-strip-types --test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"@total-typescript/ts-reset": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"@vitest/ui": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"typescript-eslint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const THEMES = ["light", "dark"] as const
|
||||
export type Theme = (typeof THEMES)[number]
|
||||
export const THEME_DEFAULT = "light" satisfies Theme
|
||||
|
||||
export const TIMEZONE = process.env["TZ"] ?? "UTC"
|
||||
export const TIMEZONE = process.env["TZ"] ?? "Europe/Paris"
|
||||
|
||||
export const BIRTH_DATE_DAY = "31"
|
||||
export const BIRTH_DATE_MONTH = "03"
|
||||
|
19
packages/utils/src/objects.ts
Normal file
19
packages/utils/src/objects.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export const deepMerge = <
|
||||
Object1 extends object,
|
||||
Object2 extends object = Object1,
|
||||
>(
|
||||
object1: Object1,
|
||||
object2: Object2,
|
||||
): Object1 & Object2 => {
|
||||
const result = { ...object1 } as Object1 & Object2
|
||||
for (const key in object2) {
|
||||
if (Object.hasOwn(object2, key)) {
|
||||
if (typeof object2[key] === "object" && object2[key] !== null) {
|
||||
result[key] = deepMerge(result[key] as any, object2[key] as any)
|
||||
} else {
|
||||
result[key] = object2[key] as any
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
describe("VERSION", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.resetModules()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('should return "0.0.0-development" when NODE_ENV is development', async () => {
|
||||
// Arrange - Given
|
||||
vi.stubEnv("NODE_ENV", "development")
|
||||
|
||||
// Act - When
|
||||
const { VERSION } = await import("../constants.ts")
|
||||
|
||||
// Assert - Then
|
||||
const expected = "0.0.0-development"
|
||||
expect(VERSION).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the version from package.json when NODE_ENV is not development", async () => {
|
||||
// Arrange - Given
|
||||
vi.stubEnv("NODE_ENV", "production")
|
||||
vi.mock("../../package.json", () => {
|
||||
return { default: { version: "1.0.0" } }
|
||||
})
|
||||
|
||||
// Act - When
|
||||
const { VERSION } = await import("../constants.ts")
|
||||
|
||||
// Assert - Then
|
||||
const expected = "1.0.0"
|
||||
expect(VERSION).toEqual(expected)
|
||||
})
|
||||
})
|
@ -1,79 +1,19 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { getISODate } from "../dates.ts"
|
||||
|
||||
import { getAge, getISODate } from "../dates.ts"
|
||||
describe("dates", () => {
|
||||
describe("getISODate", () => {
|
||||
it("should return the correct date in ISO format (e.g: 2012-05-23)", () => {
|
||||
// Arrange - Given
|
||||
const input = new Date("2012-05-23")
|
||||
|
||||
describe("getISODate", () => {
|
||||
it("should return the correct date in ISO format (e.g: 2012-05-23)", () => {
|
||||
// Arrange - Given
|
||||
const input = new Date("2012-05-23")
|
||||
// Act - When
|
||||
const output = getISODate(input)
|
||||
|
||||
// Act - When
|
||||
const output = getISODate(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "2012-05-23"
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getAge", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-02-20")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 38
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date when the birthday has not happened yet", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-07-20")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 37
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date when the birthday is today", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-03-20")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 38
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date when the birthday has not happened yet, but will happen this month", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-03-25")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 37
|
||||
expect(output).toEqual(expected)
|
||||
// Assert - Then
|
||||
const expected = "2012-05-23"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
85
packages/utils/src/tests/objects.test.ts
Normal file
85
packages/utils/src/tests/objects.test.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { deepMerge } from "../objects.ts"
|
||||
|
||||
describe("objects", () => {
|
||||
describe("deepMerge", () => {
|
||||
it("should merge two simple objects", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: 2 }
|
||||
const object2 = { b: 3, c: 4 }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: 3, c: 4 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should deeply merge nested objects", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: { x: 2, y: 3 } }
|
||||
const object2 = { b: { y: 4, z: 5 }, c: 6 }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: { x: 2, y: 4, z: 5 }, c: 6 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should overwrite primitive values", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: "hello" }
|
||||
const object2 = { a: 2, b: "world" }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 2, b: "world" }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the second object if the first is empty", () => {
|
||||
// Arrange - Given
|
||||
const object1 = {}
|
||||
const object2 = { a: 1, b: 2 }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: 2 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the first object if the second is empty", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: 2 }
|
||||
const object2 = {}
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: 2 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should handle null and undefined values correctly", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: null }
|
||||
const object2 = { b: { c: 2 }, d: undefined }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: { c: 2 }, d: undefined }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
@ -1,41 +1,43 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { capitalize } from "../strings.ts"
|
||||
|
||||
describe("capitalize", () => {
|
||||
it("should capitalize the first letter of a string", () => {
|
||||
// Arrange - Given
|
||||
const input = "hello, world!"
|
||||
describe("strings", () => {
|
||||
describe("capitalize", () => {
|
||||
it("should capitalize the first letter of a string", () => {
|
||||
// Arrange - Given
|
||||
const input = "hello, world!"
|
||||
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return an empty string when the input is an empty string", () => {
|
||||
// Arrange - Given
|
||||
const input = ""
|
||||
it("should return an empty string when the input is an empty string", () => {
|
||||
// Arrange - Given
|
||||
const input = ""
|
||||
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = ""
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
// Assert - Then
|
||||
const expected = ""
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the same string when the first letter is already capitalized", () => {
|
||||
// Arrange - Given
|
||||
const input = "Hello, world!"
|
||||
it("should return the same string when the first letter is already capitalized", () => {
|
||||
// Arrange - Given
|
||||
const input = "Hello, world!"
|
||||
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
expect(output).toEqual(expected)
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
80
packages/utils/src/tests/urls.test.ts
Normal file
80
packages/utils/src/tests/urls.test.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { LOCALE_DEFAULT } from "../constants.ts"
|
||||
import { getPathnameWithoutLocale } from "../urls.ts"
|
||||
|
||||
describe("urls", () => {
|
||||
describe("getPathnameWithoutLocale", () => {
|
||||
it("should return the pathname without the known locale prefix", () => {
|
||||
// Arrange - Given
|
||||
const input = `/${LOCALE_DEFAULT}/about`
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/about"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the same pathname when the input does not start with a known locale prefix", () => {
|
||||
// Arrange - Given
|
||||
const input = "/about"
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/about"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the same pathname when the input starts with an unknown locale prefix", () => {
|
||||
// Arrange - Given
|
||||
const input = "/abc-ABC/about"
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/abc-ABC/about"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the index route when the input is an empty string", () => {
|
||||
// Arrange - Given
|
||||
const input = ""
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the index route when the input starts with a known locale prefix and with a trailing slash", () => {
|
||||
// Arrange - Given
|
||||
const input = `/${LOCALE_DEFAULT}/`
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the index route when the input starts with a known locale prefix and without a trailing slash", () => {
|
||||
// Arrange - Given
|
||||
const input = `/${LOCALE_DEFAULT}`
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
49
packages/utils/src/types.ts
Normal file
49
packages/utils/src/types.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
|
||||
*/
|
||||
export type Primitive =
|
||||
| null
|
||||
| undefined
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| symbol
|
||||
| bigint
|
||||
|
||||
export type Satisfies<U, T extends U> = T
|
||||
|
||||
export type OmitStrict<T, K extends keyof T> = Omit<T, K>
|
||||
export type PickStrict<T, K extends keyof T> = Pick<T, K>
|
||||
|
||||
export type OverrideStrict<
|
||||
Type,
|
||||
NewType extends {
|
||||
[Key in keyof Type]?: unknown
|
||||
},
|
||||
> = Omit<Type, keyof NewType> & NewType
|
||||
|
||||
export type PartialDeep<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: PartialDeep<T[P]>
|
||||
}
|
||||
: T
|
||||
|
||||
export type Status = "error" | "idle" | "pending" | "success"
|
||||
|
||||
/**
|
||||
* Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union.
|
||||
*
|
||||
* @see https://github.com/Microsoft/TypeScript/issues/29729
|
||||
*
|
||||
* @example
|
||||
```
|
||||
// Before
|
||||
type Pet = 'dog' | 'cat' | string;
|
||||
|
||||
// After
|
||||
type Pet2 = LiteralUnion<'dog' | 'cat', string>;
|
||||
```
|
||||
*/
|
||||
export type LiteralUnion<LiteralType, BaseType extends Primitive> =
|
||||
| LiteralType
|
||||
| (BaseType & Record<never, never>)
|
18
packages/utils/src/urls.ts
Normal file
18
packages/utils/src/urls.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { LOCALES } from "./constants.ts"
|
||||
|
||||
/**
|
||||
* Get the pathname without the known locale prefix.
|
||||
* @param input
|
||||
* @returns
|
||||
* @example getRoutePathnameWithoutLocale("/fr-FR/about") // "/about"
|
||||
*/
|
||||
export const getPathnameWithoutLocale = (input: string): string => {
|
||||
const locale = LOCALES.find((locale) => {
|
||||
return input.startsWith(`/${locale}`)
|
||||
})
|
||||
const pathname = locale != null ? input.slice(locale.length + 1) : input
|
||||
if (pathname.length <= 0) {
|
||||
return `/${pathname}`
|
||||
}
|
||||
return pathname
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
enabled: true,
|
||||
provider: "v8",
|
||||
},
|
||||
},
|
||||
})
|
Reference in New Issue
Block a user