From d6674aad05d4b1d5e0541bba4f1d7ba0e3b76c60 Mon Sep 17 00:00:00 2001 From: zubeyralmaho Date: Sun, 8 Mar 2026 00:47:11 +0300 Subject: [PATCH] feat: add CI workflow and unit tests for utility functions --- .github/workflows/ci.yml | 62 +++++++++++++++++++++ tests/functions.spec.js | 114 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/functions.spec.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b69f16e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI + +on: + pull_request: + branches: [master] + push: + branches: [master] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run ESLint + run: yarn lint + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build + run: yarn build + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build (required for tests) + run: yarn build + + - name: Run tests + run: yarn test diff --git a/tests/functions.spec.js b/tests/functions.spec.js new file mode 100644 index 0000000..d3754bb --- /dev/null +++ b/tests/functions.spec.js @@ -0,0 +1,114 @@ +import { formatTimeDiff, isOutputTxt, deepCloneMap, formatMissingProjects, getMissingRequiredFiles } from '../build/utils/functions'; +import { SAVE_AS_TXT_KEYWORD } from '../build/utils/constants'; + +describe('formatTimeDiff', () => { + test('should format seconds correctly', () => { + const time = [5, 500000000]; // 5.5 seconds + const result = formatTimeDiff(time); + expect(result).toBe('5.500s'); + }); + + test('should format time over 60 seconds as minutes', () => { + const time = [125, 0]; // 2 minutes 5 seconds + const result = formatTimeDiff(time); + expect(result).toBe('2m 5.000s'); + }); + + test('should handle nanoseconds precision', () => { + const time = [0, 123456789]; + const result = formatTimeDiff(time); + expect(result).toBe('0.123s'); + }); + + test('should handle zero time', () => { + const time = [0, 0]; + const result = formatTimeDiff(time); + expect(result).toBe('0.000s'); + }); +}); + +describe('isOutputTxt', () => { + test('should return true for stdout string', () => { + expect(isOutputTxt(SAVE_AS_TXT_KEYWORD)).toBe(true); + }); + + test('should return false for other strings', () => { + expect(isOutputTxt('build')).toBe(false); + expect(isOutputTxt('dist')).toBe(false); + }); + + test('should return true when array contains stdout', () => { + expect(isOutputTxt(['build', SAVE_AS_TXT_KEYWORD])).toBe(true); + }); + + test('should return false when array does not contain stdout', () => { + expect(isOutputTxt(['build', 'dist'])).toBe(false); + }); +}); + +describe('deepCloneMap', () => { + test('should create a deep clone of Map with Set values', () => { + const original = new Map(); + original.set('key1', new Set(['a', 'b', 'c'])); + original.set('key2', new Set(['x', 'y'])); + + const cloned = deepCloneMap(original); + + // Check that values are equal + expect(cloned.get('key1')).toEqual(new Set(['a', 'b', 'c'])); + expect(cloned.get('key2')).toEqual(new Set(['x', 'y'])); + + // Verify it's a different reference + expect(cloned).not.toBe(original); + expect(cloned.get('key1')).not.toBe(original.get('key1')); + }); + + test('should handle empty map', () => { + const original = new Map(); + const cloned = deepCloneMap(original); + expect(cloned.size).toBe(0); + }); +}); + +describe('formatMissingProjects', () => { + test('should format projects with time', () => { + const projects = [ + { buildProject: 'app1', time: [5, 0] } + ]; + const result = formatMissingProjects(projects, 'Project'); + expect(result).toEqual([ + { 'Project': 'app1', 'Time': '5.000s' } + ]); + }); + + test('should format projects with execTime and cacheTime', () => { + const projects = [ + { buildProject: 'app2', execTime: [3, 0], cacheTime: [2, 0] } + ]; + const result = formatMissingProjects(projects, 'Project'); + expect(result[0]['Project']).toBe('app2'); + expect(result[0]['Total Time']).toBe('5.000s'); + }); + + test('should handle projects without time info', () => { + const projects = [ + { buildProject: 'app3' } + ]; + const result = formatMissingProjects(projects, 'Project'); + expect(result).toEqual([ + { 'Project': 'app3' } + ]); + }); +}); + +describe('getMissingRequiredFiles', () => { + test('should return empty array when requiredFiles is undefined', () => { + const result = getMissingRequiredFiles('/some/path', undefined); + expect(result).toEqual([]); + }); + + test('should return empty array when requiredFiles is empty', () => { + const result = getMissingRequiredFiles('/some/path', []); + expect(result).toEqual([]); + }); +});