Skip to content

Commit 4e032b7

Browse files
committed
ut
1 parent e2249ab commit 4e032b7

File tree

10 files changed

+898
-1
lines changed

10 files changed

+898
-1
lines changed

.github/workflows/test.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
push:
8+
branches:
9+
- master
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
test:
17+
runs-on: ubuntu-latest
18+
19+
strategy:
20+
matrix:
21+
node-version: [20.x]
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
- uses: oven-sh/setup-bun@v2
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: ${{ matrix.node-version }}
29+
registry-url: 'https://registry.npmjs.org'
30+
31+
- name: Install Dependency
32+
env:
33+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
NODE_OPTIONS: '--max_old_space_size=4096'
35+
run: bun install --frozen-lockfile
36+
37+
- name: Run unit tests with coverage
38+
env:
39+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40+
NODE_OPTIONS: '--max_old_space_size=4096'
41+
run: bun run test:coverage
42+
43+
- name: Upload coverage artifact
44+
uses: actions/upload-artifact@v4
45+
with:
46+
name: coverage
47+
path: coverage
48+
if-no-files-found: error

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
"scripts": {
2525
"build": "rm -rf lib && swc src -d lib --strip-leading-paths && tsc -p tsconfig.build.json",
2626
"prepublishOnly": "bun scripts/prepublish.ts && bun run build && chmod +x lib/index.js",
27-
"lint": "tsc --noEmit && biome check --write ."
27+
"lint": "tsc --noEmit && biome check --write .",
28+
"test": "bun test",
29+
"test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov"
2830
},
2931
"repository": {
3032
"type": "git",

tests/add-gitignore.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { addGitIgnore } from '../src/utils/add-gitignore';
6+
import { credentialFile, tempDir } from '../src/utils/constants';
7+
8+
const originalCwd = process.cwd();
9+
10+
function mkTempDir(prefix: string): string {
11+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
12+
}
13+
14+
function readGitIgnore(cwd: string): string {
15+
return fs.readFileSync(path.join(cwd, '.gitignore'), 'utf-8');
16+
}
17+
18+
describe('utils/add-gitignore', () => {
19+
let tempRoot = '';
20+
21+
beforeEach(() => {
22+
tempRoot = mkTempDir('rn-update-gitignore-');
23+
process.chdir(tempRoot);
24+
});
25+
26+
afterEach(() => {
27+
process.chdir(originalCwd);
28+
if (tempRoot && fs.existsSync(tempRoot)) {
29+
fs.rmSync(tempRoot, { recursive: true, force: true });
30+
}
31+
});
32+
33+
test('does nothing when .gitignore does not exist', () => {
34+
addGitIgnore();
35+
expect(fs.existsSync(path.join(tempRoot, '.gitignore'))).toBe(false);
36+
});
37+
38+
test('appends required ignore entries when missing', () => {
39+
fs.writeFileSync(path.join(tempRoot, '.gitignore'), 'node_modules\n');
40+
41+
addGitIgnore();
42+
43+
const content = readGitIgnore(tempRoot);
44+
expect(content).toContain('# react-native-update');
45+
expect(content).toContain(credentialFile);
46+
expect(content).toContain(tempDir);
47+
});
48+
49+
test('does not duplicate entries that already exist', () => {
50+
fs.writeFileSync(
51+
path.join(tempRoot, '.gitignore'),
52+
['node_modules', credentialFile].join('\n'),
53+
);
54+
55+
addGitIgnore();
56+
const first = readGitIgnore(tempRoot);
57+
addGitIgnore();
58+
const second = readGitIgnore(tempRoot);
59+
60+
expect(second).toEqual(first);
61+
62+
const lines = second.split('\n');
63+
expect(lines.filter((line) => line.trim() === credentialFile)).toHaveLength(
64+
1,
65+
);
66+
});
67+
});

tests/bundle-pack.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { open as openZipFile } from 'yauzl';
6+
import { packBundle } from '../src/bundle-pack';
7+
8+
function mkTempDir(prefix: string): string {
9+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
10+
}
11+
12+
async function listZipEntries(zipPath: string): Promise<string[]> {
13+
const entries: string[] = [];
14+
await new Promise<void>((resolve, reject) => {
15+
openZipFile(zipPath, { lazyEntries: true }, (error, zipfile) => {
16+
if (error || !zipfile) {
17+
reject(error ?? new Error('Failed to open zip file'));
18+
return;
19+
}
20+
21+
zipfile.on('entry', (entry) => {
22+
entries.push(entry.fileName);
23+
zipfile.readEntry();
24+
});
25+
zipfile.once('end', () => resolve());
26+
zipfile.once('error', reject);
27+
28+
zipfile.readEntry();
29+
});
30+
});
31+
return entries;
32+
}
33+
34+
describe('bundle-pack', () => {
35+
let tempRoot = '';
36+
37+
beforeEach(() => {
38+
tempRoot = mkTempDir('rn-update-pack-bundle-');
39+
});
40+
41+
afterEach(() => {
42+
if (tempRoot && fs.existsSync(tempRoot)) {
43+
fs.rmSync(tempRoot, { recursive: true, force: true });
44+
}
45+
});
46+
47+
test('packs bundle and excludes ignored files', async () => {
48+
const sourceDir = path.join(tempRoot, 'bundle');
49+
const nestedDir = path.join(sourceDir, 'assets');
50+
fs.mkdirSync(nestedDir, { recursive: true });
51+
52+
fs.writeFileSync(path.join(sourceDir, 'index.bundlejs'), 'bundle');
53+
fs.writeFileSync(path.join(sourceDir, 'index.bundlejs.map'), 'ignored-map');
54+
fs.writeFileSync(
55+
path.join(sourceDir, 'bundle.harmony.js.map'),
56+
'ignored-map',
57+
);
58+
fs.writeFileSync(path.join(sourceDir, 'keep.txt'), 'keep');
59+
fs.writeFileSync(path.join(sourceDir, 'ignored.txt.map'), 'ignored-map');
60+
fs.writeFileSync(path.join(sourceDir, '.DS_Store'), 'ignored');
61+
fs.writeFileSync(path.join(nestedDir, 'image.png'), 'image-content');
62+
63+
const outputZip = path.join(tempRoot, 'dist', 'output.ppk');
64+
await packBundle(sourceDir, outputZip);
65+
66+
expect(fs.existsSync(outputZip)).toBe(true);
67+
68+
const entries = await listZipEntries(outputZip);
69+
expect(entries).toContain('index.bundlejs');
70+
expect(entries).toContain('keep.txt');
71+
expect(entries).toContain('assets/');
72+
expect(entries).toContain('assets/image.png');
73+
74+
expect(entries).not.toContain('index.bundlejs.map');
75+
expect(entries).not.toContain('bundle.harmony.js.map');
76+
expect(entries).not.toContain('ignored.txt.map');
77+
expect(entries).not.toContain('.DS_Store');
78+
});
79+
});

tests/check-lockfile.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { checkLockFiles } from '../src/utils/check-lockfile';
6+
7+
const originalCwd = process.cwd();
8+
9+
function mkTempDir(prefix: string): string {
10+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
11+
}
12+
13+
function writeJson(filePath: string, value: unknown): void {
14+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
15+
}
16+
17+
describe('utils/check-lockfile', () => {
18+
let tempRoot = '';
19+
20+
beforeEach(() => {
21+
tempRoot = mkTempDir('rn-update-lockfile-');
22+
});
23+
24+
afterEach(() => {
25+
process.chdir(originalCwd);
26+
if (tempRoot && fs.existsSync(tempRoot)) {
27+
fs.rmSync(tempRoot, { recursive: true, force: true });
28+
}
29+
});
30+
31+
test('passes when exactly one lock file exists in current directory', () => {
32+
process.chdir(tempRoot);
33+
fs.writeFileSync(path.join(tempRoot, 'bun.lock'), '');
34+
35+
expect(() => checkLockFiles()).not.toThrow();
36+
});
37+
38+
test('throws when multiple lock files exist in current directory', () => {
39+
process.chdir(tempRoot);
40+
fs.writeFileSync(path.join(tempRoot, 'bun.lock'), '');
41+
fs.writeFileSync(path.join(tempRoot, 'yarn.lock'), '');
42+
43+
expect(() => checkLockFiles()).toThrow();
44+
});
45+
46+
test('detects lock file from monorepo root when cwd has none', () => {
47+
const monorepoRoot = path.join(tempRoot, 'repo');
48+
const packageDir = path.join(monorepoRoot, 'packages', 'cli');
49+
fs.mkdirSync(packageDir, { recursive: true });
50+
writeJson(path.join(monorepoRoot, 'package.json'), {
51+
private: true,
52+
workspaces: ['packages/*'],
53+
});
54+
fs.writeFileSync(path.join(monorepoRoot, 'pnpm-lock.yaml'), '');
55+
56+
process.chdir(packageDir);
57+
expect(() => checkLockFiles()).not.toThrow();
58+
});
59+
60+
test('warns but does not throw when no lock file is found', () => {
61+
process.chdir(tempRoot);
62+
writeJson(path.join(tempRoot, 'package.json'), { name: 'no-lock' });
63+
64+
const warnings: string[] = [];
65+
const originalWarn = console.warn;
66+
console.warn = (...args: unknown[]) => {
67+
warnings.push(args.map((item) => String(item)).join(' '));
68+
};
69+
70+
try {
71+
expect(() => checkLockFiles()).not.toThrow();
72+
expect(warnings.length).toBeGreaterThan(0);
73+
} finally {
74+
console.warn = originalWarn;
75+
}
76+
});
77+
});

tests/command-result.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, test } from 'bun:test';
2+
import {
3+
runAsCommandResult,
4+
toErrorMessage,
5+
} from '../src/utils/command-result';
6+
7+
describe('utils/command-result', () => {
8+
test('toErrorMessage uses Error.message when possible', () => {
9+
expect(toErrorMessage(new Error('boom'), 'fallback')).toBe('boom');
10+
});
11+
12+
test('toErrorMessage falls back for non-Error values', () => {
13+
expect(toErrorMessage('boom', 'fallback')).toBe('fallback');
14+
expect(toErrorMessage({ message: 'boom' }, 'fallback')).toBe('fallback');
15+
});
16+
17+
test('runAsCommandResult returns success with raw data', async () => {
18+
const result = await runAsCommandResult(async () => 123, 'failed');
19+
expect(result).toEqual({
20+
success: true,
21+
data: 123,
22+
});
23+
});
24+
25+
test('runAsCommandResult maps successful data when mapper provided', async () => {
26+
const result = await runAsCommandResult(
27+
async () => ({ id: 1, name: 'pkg' }),
28+
'failed',
29+
(value) => ({ label: `${value.id}-${value.name}` }),
30+
);
31+
expect(result).toEqual({
32+
success: true,
33+
data: { label: '1-pkg' },
34+
});
35+
});
36+
37+
test('runAsCommandResult returns thrown Error message', async () => {
38+
const result = await runAsCommandResult(async () => {
39+
throw new Error('api failed');
40+
}, 'fallback');
41+
expect(result).toEqual({
42+
success: false,
43+
error: 'api failed',
44+
});
45+
});
46+
47+
test('runAsCommandResult returns fallback for non-Error throw', async () => {
48+
const result = await runAsCommandResult(async () => {
49+
throw 'failed';
50+
}, 'fallback');
51+
expect(result).toEqual({
52+
success: false,
53+
error: 'fallback',
54+
});
55+
});
56+
});

0 commit comments

Comments
 (0)