1
1
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:
2025-02-08 20:00:47 +01:00
parent 270920111a
commit b63cc3a66e
69 changed files with 3393 additions and 5914 deletions

View File

@ -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"],

View File

@ -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:"
}
}

View File

@ -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"

View 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
}

View File

@ -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)
})
})

View File

@ -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)
})
})
})

View 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)
})
})
})

View File

@ -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)
})
})
})

View 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)
})
})
})

View 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>)

View 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
}

View File

@ -1,10 +0,0 @@
import { defineConfig } from "vitest/config"
export default defineConfig({
test: {
coverage: {
enabled: true,
provider: "v8",
},
},
})