From 2bab21c52a8adefd7a73ddd710b55ce139d86297 Mon Sep 17 00:00:00 2001 From: Jay Singh <140599484+mathdebate09@users.noreply.github.com> Date: Sun, 3 May 2026 14:23:44 +0530 Subject: [PATCH 1/3] test: extract tests into root && cover all features --- .gitignore | 1 + package.json | 20 ++++-- src/__tests__/index.test.tsx | 1 - tests/border-effects.test.ts | 132 +++++++++++++++++++++++++++++++++++ tests/flexbox.test.ts | 87 +++++++++++++++++++++++ tests/layout.test.ts | 92 ++++++++++++++++++++++++ tests/sizing.test.ts | 70 +++++++++++++++++++ tests/spacing.test.ts | 100 ++++++++++++++++++++++++++ tests/typography.test.ts | 74 ++++++++++++++++++++ tsconfig.json | 4 +- 10 files changed, 574 insertions(+), 7 deletions(-) delete mode 100644 src/__tests__/index.test.tsx create mode 100644 tests/border-effects.test.ts create mode 100644 tests/flexbox.test.ts create mode 100644 tests/layout.test.ts create mode 100644 tests/sizing.test.ts create mode 100644 tests/spacing.test.ts create mode 100644 tests/typography.test.ts diff --git a/.gitignore b/.gitignore index fd6a46c..2a5393d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ npm-debug.log yarn-debug.log yarn-error.log lib/ +coverage/ build-report.md # Yarn diff --git a/package.json b/package.json index d158cad..12273ca 100644 --- a/package.json +++ b/package.json @@ -80,11 +80,23 @@ "registry": "https://registry.npmjs.org/" }, "jest": { - "preset": "ts-jest", + "preset": "ts-jest/presets/default-esm", "testEnvironment": "node", - "modulePathIgnorePatterns": [ - "/lib/" - ] + "testMatch": ["/tests/**/*.test.ts"], + "extensionsToTreatAsEsm": [".ts"], + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + }, + "transform": { + "^.+\\.ts$": [ + "ts-jest", + { + "useESM": true, + "tsconfig": "tsconfig.json" + } + ] + }, + "modulePathIgnorePatterns": ["/lib/"] }, "commitlint": { "extends": [ diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx deleted file mode 100644 index bf84291..0000000 --- a/src/__tests__/index.test.tsx +++ /dev/null @@ -1 +0,0 @@ -it.todo('write a test'); diff --git a/tests/border-effects.test.ts b/tests/border-effects.test.ts new file mode 100644 index 0000000..2a7e375 --- /dev/null +++ b/tests/border-effects.test.ts @@ -0,0 +1,132 @@ +import { bdr, fx, shadow } from '../src/index'; +import colorList from '../src/constants/colorList'; + +describe('bdr', () => { + const border = bdr as Record; + + it('color_(hex) sets borderColor', () => { + expect((border.color_ as (c: string) => unknown)('#ff00aa')).toEqual({ + borderColor: '#ff00aa', + }); + }); + + it('w_(n) sets borderWidth', () => { + expect((border.w_ as (n: number | string) => unknown)(3)).toEqual({ + borderWidth: 3, + }); + }); + + it('rounded_(n) sets borderRadius', () => { + expect((border.rounded_ as (n: number | string) => unknown)(10)).toEqual({ + borderRadius: 10, + }); + }); + + it('rounded_md preset', () => { + expect(border.rounded_md).toEqual({ borderRadius: 6 }); + }); + + it('palette color_red_500 matches colorList', () => { + expect(border.color_red_500).toEqual({ + borderColor: colorList.red_500, + }); + }); + + it('t_w_(n) sets borderTopWidth', () => { + expect((border.t_w_ as (n: number | string) => unknown)(4)).toEqual({ + borderTopWidth: 4, + }); + }); +}); + +describe('fx', () => { + const effects = fx as Record; + + it('bg_color_(hex) sets backgroundColor', () => { + expect((effects.bg_color_ as (c: string) => unknown)('#112233')).toEqual({ + backgroundColor: '#112233', + }); + }); + + it('palette bg_color_red_500', () => { + expect(effects.bg_color_red_500).toEqual({ + backgroundColor: colorList.red_500, + }); + }); + + it('opacity_(valid) sets opacity', () => { + expect((effects.opacity_ as (n: number | string) => unknown)(0.5)).toEqual({ + opacity: 0.5, + }); + }); + + it('opacity_(out of range) throws', () => { + const opacity = effects.opacity_ as (n: number | string) => unknown; + expect(() => opacity(-0.1)).toThrow(/between 0\.0 and 1\.0/); + expect(() => opacity(1.1)).toThrow(/between 0\.0 and 1\.0/); + }); + + it('opacity_5 preset maps to 0.5', () => { + expect(effects.opacity_5).toEqual({ opacity: 0.5 }); + }); + + it('elevation_3 preset', () => { + expect(effects.elevation_3).toEqual({ elevation: 3 }); + }); + + it('elevation_(n) dynamic', () => { + expect((effects.elevation_ as (n: number | string) => unknown)(5)).toEqual({ + elevation: 5, + }); + }); + + it('elevation_(out of range) throws', () => { + const elevation = effects.elevation_ as (n: number | string) => unknown; + expect(() => elevation(0)).toThrow(/between 1 and 10/); + expect(() => elevation(11)).toThrow(/between 1 and 10/); + }); +}); + +describe('shadow', () => { + const shadowMap = shadow as Record; + + it('color_(hex) sets shadowColor', () => { + expect((shadowMap.color_ as (c: string) => unknown)('#abcdef')).toEqual({ + shadowColor: '#abcdef', + }); + }); + + it('offset_(w,h) sets shadowOffset', () => { + expect((shadowMap.offset_ as (w: number | string, h: number | string) => unknown)(3, 7)).toEqual({ + shadowOffset: { width: 3, height: 7 }, + }); + }); + + it('opacity_(n) dynamic sets shadowOpacity', () => { + expect((shadowMap.opacity_ as (n: number | string) => unknown)(0.25)).toEqual({ + shadowOpacity: 0.25, + }); + }); + + it('rounded_(n) sets shadowRadius', () => { + expect((shadowMap.rounded_ as (n: number | string) => unknown)(9)).toEqual({ + shadowRadius: 9, + }); + }); + + it('rounded_md preset', () => { + expect(shadowMap.rounded_md).toEqual({ shadowRadius: 3 }); + }); + + it('palette color_red_500', () => { + expect(shadowMap.color_red_500).toEqual({ + shadowColor: colorList.red_500, + }); + }); + + it('offset_2 preset', () => { + expect(shadowMap.offset_2).toEqual({ + shadowOffset: { width: 2, height: 2 }, + }); + }); +}); diff --git a/tests/flexbox.test.ts b/tests/flexbox.test.ts new file mode 100644 index 0000000..7b1a264 --- /dev/null +++ b/tests/flexbox.test.ts @@ -0,0 +1,87 @@ +import { flex, align, justify, place } from '../src/index'; +import { spacingScale } from '../src/constants/spacingScale'; + +describe('flex', () => { + const flexMap = flex as Record; + + it('f_3 preset sets flex grow integer', () => { + expect(flexMap.f_3).toEqual({ flex: 3 }); + }); + + it('f_(n) dynamic sets flex', () => { + expect((flexMap.f_ as (n: number | string) => unknown)(7)).toEqual({ flex: 7 }); + }); + + it('gap_4 uses spacing scale for gap', () => { + expect(flexMap.gap_4).toEqual({ + gap: spacingScale['4'], + }); + }); + + it('gap_(n) dynamic sets gap', () => { + expect((flexMap.gap_ as (n: number | string) => unknown)(12)).toEqual({ gap: 12 }); + }); + + it('gap_x_ sets rowGap from scale', () => { + expect(flexMap.gap_x_2).toEqual({ + rowGap: spacingScale['2'], + }); + }); + + it('gap_y_ sets columnGap from scale', () => { + expect(flexMap.gap_y_2).toEqual({ + columnGap: spacingScale['2'], + }); + }); + + it('basis_4 uses spacing scale for flexBasis', () => { + expect(flexMap.basis_4).toEqual({ + flexBasis: spacingScale['4'], + }); + }); + + it('basis_auto preset', () => { + expect(flexMap.basis_auto).toEqual({ flexBasis: 'auto' }); + }); + + it("basis_('auto') returns string auto", () => { + expect((flexMap.basis_ as (v: string | number) => unknown)('auto')).toEqual({ + flexBasis: 'auto', + }); + }); + + it('basis_(n) coerces to number', () => { + expect((flexMap.basis_ as (v: string | number) => unknown)(33)).toEqual({ + flexBasis: 33, + }); + }); +}); + +describe('align', () => { + it('items_* maps alignItems', () => { + expect(align.items_center).toEqual({ alignItems: 'center' }); + }); + + it('content_* maps alignContent', () => { + expect(align.content_between).toEqual({ alignContent: 'space-between' }); + }); + + it('self_* maps alignSelf', () => { + expect(align.self_stretch).toEqual({ alignSelf: 'stretch' }); + }); +}); + +describe('justify', () => { + it('maps justifyContent', () => { + expect(justify.evenly).toEqual({ justifyContent: 'space-evenly' }); + }); +}); + +describe('place', () => { + it('compound items_center aligns and justifies center', () => { + expect(place.items_center).toEqual({ + alignItems: 'center', + justifyContent: 'center', + }); + }); +}); diff --git a/tests/layout.test.ts b/tests/layout.test.ts new file mode 100644 index 0000000..daf08c7 --- /dev/null +++ b/tests/layout.test.ts @@ -0,0 +1,92 @@ +import { aspect, object_fit, display, direction, pos, z, overflow } from '../src/index'; +import { spacingScale } from '../src/constants/spacingScale'; + +describe('aspect', () => { + const custom = aspect.custom_ as (key: string | number) => { aspectRatio: number | string }; + + it('preset square uses numeric ratio 1', () => { + expect(aspect.square).toEqual({ aspectRatio: 1 }); + }); + + it('preset video uses 16/9', () => { + expect(aspect.video).toEqual({ aspectRatio: 16 / 9 }); + }); + + it('custom_ resolves fractional string', () => { + expect(custom('4/3')).toEqual({ aspectRatio: 4 / 3 }); + }); + + it('custom_(number) passes ratio through', () => { + expect(custom(3)).toEqual({ aspectRatio: 3 }); + }); + + it('custom_(numeric string) coerces to number', () => { + expect(custom('5')).toEqual({ aspectRatio: 5 }); + }); +}); + +describe('object_fit', () => { + it('cover uses resizeMode', () => { + expect(object_fit.cover).toEqual({ resizeMode: 'cover' }); + }); + + it('fill uses objectFit', () => { + expect(object_fit.fill).toEqual({ objectFit: 'fill' }); + }); + + it('scale_down uses objectFit', () => { + expect(object_fit.scale_down).toEqual({ objectFit: 'scale_down' }); + }); +}); + +describe('display', () => { + it('flex sets display flex', () => { + expect(display.flex).toEqual({ display: 'flex' }); + }); +}); + +describe('direction', () => { + it('ltr sets direction', () => { + expect(direction.ltr).toEqual({ direction: 'ltr' }); + }); +}); + +describe('overflow', () => { + it('hidden sets overflow hidden', () => { + expect(overflow.hidden).toEqual({ overflow: 'hidden' }); + }); +}); + +describe('pos', () => { + it('absolute preset sets position', () => { + expect(pos.absolute).toEqual({ position: 'absolute' }); + }); + + it('t_4 uses spacing scale for top', () => { + expect((pos as Record).t_4).toEqual({ + top: spacingScale['4'], + }); + }); + + it('t_(value) function form uses numeric top', () => { + expect(pos.t_(20)).toEqual({ top: 20 }); + }); +}); + +describe('z', () => { + it('index_10 preset', () => { + expect(z.index_10).toEqual({ zIndex: 10 }); + }); + + it('index_auto preset', () => { + expect(z.index_auto).toEqual({ zIndex: 'auto' }); + }); + + it('index_(number)', () => { + expect(z.index_(10)).toEqual({ zIndex: 10 }); + }); + + it("index_('auto')", () => { + expect(z.index_('auto')).toEqual({ zIndex: 'auto' }); + }); +}); diff --git a/tests/sizing.test.ts b/tests/sizing.test.ts new file mode 100644 index 0000000..3aebcac --- /dev/null +++ b/tests/sizing.test.ts @@ -0,0 +1,70 @@ +import { h, w, size } from '../src/index'; +import { spacingScale } from '../src/constants/spacingScale'; + +describe('h (height)', () => { + const height = h as Record; + + it('h_4 maps scale to height', () => { + expect(height.h_4).toEqual({ height: spacingScale['4'] }); + }); + + it('h_full preset', () => { + expect(height.h_full).toEqual({ height: '100%' }); + }); + + it('h_auto preset', () => { + expect(height.h_auto).toEqual({ height: 'auto' }); + }); + + it('w_(value) sets arbitrary height (named w_ on h object)', () => { + expect((h as { w_: (v: string | number) => unknown }).w_('33%')).toEqual({ + height: '33%', + }); + }); + + it('max_(n) sets maxHeight', () => { + expect((h as { max_: (v: string | number) => unknown }).max_(200)).toEqual({ + maxHeight: 200, + }); + }); +}); + +describe('w (width)', () => { + const widthMap = w as Record; + + it('w_4 maps scale to width', () => { + expect(widthMap.w_4).toEqual({ width: spacingScale['4'] }); + }); + + it('w_auto preset', () => { + expect(widthMap.w_auto).toEqual({ width: 'auto' }); + }); + + it('w_(value) sets arbitrary width', () => { + expect((widthMap.w_ as (v: number | string) => unknown)(180)).toEqual({ width: 180 }); + }); + + it('max_(n) sets maxWidth', () => { + expect((widthMap.max_ as (v: number | string) => unknown)(320)).toEqual({ + maxWidth: 320, + }); + }); +}); + +describe('size', () => { + const sizeMap = size as Record; + + it('s_4 maps scale to width and height', () => { + expect(sizeMap.s_4).toEqual({ + width: spacingScale['4'], + height: spacingScale['4'], + }); + }); + + it('s_(value) sets matching width and height', () => { + expect((sizeMap.s_ as (v: number | string) => unknown)('50%')).toEqual({ + width: '50%', + height: '50%', + }); + }); +}); diff --git a/tests/spacing.test.ts b/tests/spacing.test.ts new file mode 100644 index 0000000..7a1ada7 --- /dev/null +++ b/tests/spacing.test.ts @@ -0,0 +1,100 @@ +import { m, p } from '../src/index'; +import { spacingScale } from '../src/constants/spacingScale'; +import type { Margin, Padding } from '../src/types/spacing'; +import type { DynamicArg } from '../src/types/maps'; + +describe('m (margin)', () => { + const margin = m as Record; + const mFn = m.m_ as (...keys: DynamicArg[]) => Margin; + + it('maps scale token mt_4 to spacingScale value', () => { + expect(margin.mt_4).toEqual({ marginTop: spacingScale['4'] }); + }); + + it('maps special scale key px on ml_px', () => { + expect(margin.ml_px).toEqual({ marginLeft: spacingScale.px }); + }); + + it('variadic m_(numeric) passes number through without scale lookup', () => { + expect(mFn(42)).toEqual({ margin: 42 }); + }); + + it('axis fn mt_(numeric) passes number through', () => { + expect((m.mt_ as (k: DynamicArg) => Margin)(42)).toEqual({ marginTop: 42 }); + }); + + it('parses string integer not on scale', () => { + expect((m.mt_ as (k: DynamicArg) => Margin)('37')).toEqual({ marginTop: 37 }); + }); + + it('supports auto via mx_auto', () => { + expect(margin.mx_auto).toEqual({ + marginHorizontal: 'auto', + }); + }); + + it('variadic m_ with two keys sets vertical and horizontal', () => { + expect(mFn(4, 8)).toEqual({ + marginVertical: 4, + marginHorizontal: 8, + }); + }); + + it('variadic m_ with four keys sets all sides', () => { + expect(mFn(1, 2, 3, 4)).toEqual({ + marginTop: 1, + marginRight: 2, + marginBottom: 3, + marginLeft: 4, + }); + }); + + it('ms_ resolves marginStart from scale', () => { + expect(margin.ms_2).toEqual({ marginStart: spacingScale['2'] }); + }); + + it('me_ resolves marginEnd from scale', () => { + expect(margin.me_2).toEqual({ marginEnd: spacingScale['2'] }); + }); + + it('returns undefined for invalid static scale key', () => { + expect(margin.mt_not_a_number).toBeUndefined(); + }); +}); + +describe('p (padding)', () => { + const padding = p as Record; + const pFn = p.p_ as (...keys: DynamicArg[]) => Padding; + + it('maps scale token pt_4 to spacingScale value', () => { + expect(padding.pt_4).toEqual({ paddingTop: spacingScale['4'] }); + }); + + it('variadic p_(numeric) passes number through', () => { + expect(pFn(42)).toEqual({ padding: 42 }); + }); + + it('supports auto literal via scaling resolver', () => { + expect(padding.p_auto).toEqual({ padding: 'auto' }); + }); + + it('variadic p_ with two keys sets vertical and horizontal', () => { + expect(pFn(4, 8)).toEqual({ + paddingVertical: 4, + paddingHorizontal: 8, + }); + }); + + it('variadic p_ with four keys sets all sides', () => { + expect(pFn(1, 2, 3, 4)).toEqual({ + paddingTop: 1, + paddingRight: 2, + paddingBottom: 3, + paddingLeft: 4, + }); + }); + + it('ps_ resolves paddingStart from scale', () => { + expect(padding.ps_2).toEqual({ paddingStart: spacingScale['2'] }); + }); +}); diff --git a/tests/typography.test.ts b/tests/typography.test.ts new file mode 100644 index 0000000..a3c984c --- /dev/null +++ b/tests/typography.test.ts @@ -0,0 +1,74 @@ +import { text, decoration } from '../src/index'; +import colorList from '../src/constants/colorList'; + +describe('text', () => { + const textMap = text as Record; + + it('color_(hex) sets color', () => { + expect((textMap.color_ as (c: string) => unknown)('#00ff00')).toEqual({ + color: '#00ff00', + }); + }); + + it('palette color_red_500', () => { + expect(textMap.color_red_500).toEqual({ color: colorList.red_500 }); + }); + + it('italic snippet sets fontStyle', () => { + expect(textMap.italic).toEqual({ fontStyle: 'italic' }); + }); + + it('fs_base preset sets fontSize and lineHeight', () => { + expect(textMap.fs_base).toEqual({ fontSize: 16, lineHeight: 24 }); + }); + + it('shadow_color_(hex)', () => { + expect((textMap.shadow_color_ as (c: string) => unknown)('#010203')).toEqual({ + textShadowColor: '#010203', + }); + }); + + it('shadow_offset_(w,h)', () => { + expect((textMap.shadow_offset_ as (w: number | string, h: number | string) => unknown)(4, 8)).toEqual({ + textShadowOffset: { width: 4, height: 8 }, + }); + }); + + it('shadow_radius_(n)', () => { + expect((textMap.shadow_radius_ as (n: number | string) => unknown)(6)).toEqual({ + textShadowRadius: 6, + }); + }); + + it('palette shadow_color_red_500', () => { + expect(textMap.shadow_color_red_500).toEqual({ + textShadowColor: colorList.red_500, + }); + }); + + it('shadow_radius_3 preset', () => { + expect(textMap.shadow_radius_3).toEqual({ textShadowRadius: 3 }); + }); +}); + +describe('decoration', () => { + const deco = decoration as Record; + + it('underline preset sets textDecorationLine', () => { + expect(deco.underline).toEqual({ + textDecorationLine: 'underline', + }); + }); + + it('color_(hex) sets textDecorationColor', () => { + expect((deco.color_ as (c: string) => unknown)('#445566')).toEqual({ + textDecorationColor: '#445566', + }); + }); + + it('palette color_red_500', () => { + expect(deco.color_red_500).toEqual({ + textDecorationColor: colorList.red_500, + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 518c917..dc00bd8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,10 +11,10 @@ "skipLibCheck": true, "esModuleInterop": true, "ignoreDeprecations": "6.0", - "rootDir": "./src", + "rootDir": ".", "outDir": "lib", "types": ["jest", "node"] }, - "include": ["src"], + "include": ["src/**/*.ts", "tests/**/*.ts"], "exclude": ["node_modules", "lib", "example"] } From ebb095b10bcc978119b9172193388c8f3912694c Mon Sep 17 00:00:00 2001 From: Jay Singh <140599484+mathdebate09@users.noreply.github.com> Date: Sun, 3 May 2026 14:30:58 +0530 Subject: [PATCH 2/3] ci: tests to labelling list --- .github/labeler.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index ec1e051..96c5466 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -27,3 +27,6 @@ 'CI': - '.github/**/*' + +'Tests': + - 'tests/*' From db64b776372cbb9c00b1cd3b3e1a607a8716b029 Mon Sep 17 00:00:00 2001 From: Jay Singh <140599484+mathdebate09@users.noreply.github.com> Date: Sun, 3 May 2026 14:36:18 +0530 Subject: [PATCH 3/3] ci: bugs --- .github/workflows/pr-build-report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-build-report.yml b/.github/workflows/pr-build-report.yml index 8e68733..26ce7c9 100644 --- a/.github/workflows/pr-build-report.yml +++ b/.github/workflows/pr-build-report.yml @@ -16,8 +16,6 @@ concurrency: jobs: report: - # Fork PRs cannot update comments with the default token; skip to avoid a failing step. - if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest steps: - name: Checkout @@ -41,7 +39,9 @@ jobs: RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: node scripts/pr-build-report.mjs + # Fork PRs: GITHUB_TOKEN cannot write PR comments on the base repo; skip only this step. - name: Upsert PR comment + if: github.event.pull_request.head.repo.full_name == github.repository uses: actions/github-script@v7 with: script: |