@proulr/sdui-components 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proulr/sdui-components",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "main": "./src/renderer/index.ts",
6
6
  "types": "./src/renderer/index.ts",
@@ -23,6 +23,8 @@
23
23
  "scripts": {
24
24
  "prepare": "husky",
25
25
  "typecheck": "tsc --noEmit",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
26
28
  "prepublishOnly": "npm run typecheck",
27
29
  "lint": "eslint .",
28
30
  "lint:fix": "eslint . --fix",
@@ -32,7 +34,7 @@
32
34
  "access": "public"
33
35
  },
34
36
  "dependencies": {
35
- "@proulr/sdui-contract": "^0.1.4"
37
+ "@proulr/sdui-contract": "^0.1.5"
36
38
  },
37
39
  "peerDependencies": {
38
40
  "expo-blur": ">=15",
@@ -76,7 +78,8 @@
76
78
  "react-native": "0.85.3",
77
79
  "react-native-google-mobile-ads": "^16.3.4",
78
80
  "react-native-safe-area-context": "~5.7.0",
79
- "typescript": "~6.0.3"
81
+ "typescript": "~6.0.3",
82
+ "vitest": "^3.2.7"
80
83
  },
81
84
  "engines": {
82
85
  "node": ">=22"
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { compileBdcToWire } from './compile-bdc-to-wire';
3
+ import type { BdcElement } from './jsx-runtime';
4
+
5
+ function screen(props: Record<string, unknown>, children: BdcElement[]): BdcElement {
6
+ return { type: 'screen', props, children };
7
+ }
8
+
9
+ describe('compileBdcToWire', () => {
10
+ it('compiles a minimal screen tree', () => {
11
+ const wire = compileBdcToWire(
12
+ screen({ id: 'demo.home', data: { n: 1 } }, [
13
+ {
14
+ type: 'text',
15
+ props: { text: 'Hello' },
16
+ children: [],
17
+ },
18
+ ]),
19
+ );
20
+
21
+ expect(wire.schema_version).toBe(1);
22
+ expect(wire.screen_id).toBe('demo.home');
23
+ expect(wire.data).toEqual({ n: 1 });
24
+ expect(wire.blocks[0]).toMatchObject({
25
+ type: 'text',
26
+ props: { text: 'Hello' },
27
+ });
28
+ });
29
+
30
+ it('maps camelCase props to snake_case', () => {
31
+ const wire = compileBdcToWire(
32
+ screen({ id: 'demo.home' }, [
33
+ {
34
+ type: 'button',
35
+ props: { actionId: 'go', label: 'Go' },
36
+ children: [],
37
+ },
38
+ ]),
39
+ );
40
+ expect(wire.blocks[0].props).toMatchObject({ action_id: 'go', label: 'Go' });
41
+ });
42
+
43
+ it('rejects non-screen roots', () => {
44
+ expect(() =>
45
+ compileBdcToWire({ type: 'stack', props: {}, children: [] }),
46
+ ).toThrow(/bdc_root_must_be_screen/);
47
+ });
48
+ });
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { brToISO, isValidDateBR, maskDateBR } from './brazilian-date-input';
3
+
4
+ describe('brazilian-date-input', () => {
5
+ it('masks digits as DD/MM/YYYY', () => {
6
+ expect(maskDateBR('01022000')).toBe('01/02/2000');
7
+ expect(maskDateBR('01')).toBe('01');
8
+ });
9
+
10
+ it('validates calendar dates', () => {
11
+ expect(isValidDateBR('01/02/2000')).toBe(true);
12
+ expect(isValidDateBR('31/02/2000')).toBe(false);
13
+ expect(isValidDateBR('1/2/2000')).toBe(false);
14
+ });
15
+
16
+ it('converts BR date to ISO', () => {
17
+ expect(brToISO('01/02/2000')).toBe('2000-02-01');
18
+ expect(brToISO('bad')).toBeNull();
19
+ });
20
+ });
@@ -0,0 +1,45 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import {
3
+ formatDistanceLabel,
4
+ getPresenceStatus,
5
+ profileAgeFromBirthDate,
6
+ } from './discover-profile-formatters';
7
+
8
+ describe('profileAgeFromBirthDate', () => {
9
+ it('returns null for empty or invalid dates', () => {
10
+ expect(profileAgeFromBirthDate(null)).toBeNull();
11
+ expect(profileAgeFromBirthDate('not-a-date')).toBeNull();
12
+ });
13
+
14
+ it('computes age from ISO birth date', () => {
15
+ expect(profileAgeFromBirthDate('2000-01-01')).toBeGreaterThanOrEqual(25);
16
+ });
17
+ });
18
+
19
+ describe('formatDistanceLabel', () => {
20
+ it('formats metric distances', () => {
21
+ expect(formatDistanceLabel(500)).toBe('500 m');
22
+ expect(formatDistanceLabel(1500)).toBe('1.5 km');
23
+ expect(formatDistanceLabel(15000)).toBe('15 km');
24
+ });
25
+
26
+ it('formats imperial distances', () => {
27
+ expect(formatDistanceLabel(10, 'imperial')).toMatch(/ft$/);
28
+ expect(formatDistanceLabel(2000, 'imperial')).toMatch(/mi$/);
29
+ });
30
+ });
31
+
32
+ describe('getPresenceStatus', () => {
33
+ afterEach(() => {
34
+ vi.useRealTimers();
35
+ });
36
+
37
+ it('classifies lastSeen windows', () => {
38
+ vi.useFakeTimers();
39
+ vi.setSystemTime(new Date('2026-01-01T12:00:00Z'));
40
+ expect(getPresenceStatus(null)).toBe('offline');
41
+ expect(getPresenceStatus('2026-01-01T11:58:00Z')).toBe('online');
42
+ expect(getPresenceStatus('2026-01-01T06:00:00Z')).toBe('recent');
43
+ expect(getPresenceStatus('2025-12-01T12:00:00Z')).toBe('offline');
44
+ });
45
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveDataRef, resolveDataRefBoolean, resolveDataRefString } from './resolve-data-ref';
3
+
4
+ describe('resolveDataRef', () => {
5
+ const data = { user: { name: 'Ada', flags: { admin: true } } };
6
+
7
+ it('walks dotted paths', () => {
8
+ expect(resolveDataRef(data, 'user.name')).toBe('Ada');
9
+ expect(resolveDataRef(data, 'missing.path')).toBeUndefined();
10
+ expect(resolveDataRef(data, undefined)).toBeUndefined();
11
+ });
12
+
13
+ it('coerces string and boolean refs', () => {
14
+ expect(resolveDataRefString(data, 'user.name')).toBe('Ada');
15
+ expect(resolveDataRefString(data, 'missing')).toBe('');
16
+ expect(resolveDataRefBoolean(data, 'user.flags.admin')).toBe(true);
17
+ expect(resolveDataRefBoolean(data, 'user.name')).toBe(false);
18
+ });
19
+ });