diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 282794af3..8fd67b8e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ jobs: node-version: '12.x' - uses: actions/checkout@v2 with: + # needed for yarn version check, checks out entire repo fetch-depth: 0 - name: Check yarn cache integrity run: yarn install --immutable --immutable-cache --check-cache diff --git a/package.json b/package.json index d09aab8b1..bd6bf936d 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "clean": "lerna run clean", "prepare": "lerna run prepare", "prettier": "prettier --config .prettierrc --write **/*.js", - "test": "lerna run test" + "test": "lerna run test", + "test:ci": "yarn install --immutable --immutable-cache --check-cache && yarn version check && yarn build && yarn test --ignore='@lowdefy/format'" }, "devDependencies": { "@yarnpkg/pnpify": "2.3.3", diff --git a/packages/cli/.babelrc b/packages/cli/.babelrc index 41046be8a..16c7739be 100644 --- a/packages/cli/.babelrc +++ b/packages/cli/.babelrc @@ -7,6 +7,7 @@ "node": "12" } } - ] + ], + "@babel/preset-react" ] } diff --git a/packages/cli/src/commands/build/build.js b/packages/cli/src/commands/build/build.js index 7c0024415..8690e435d 100644 --- a/packages/cli/src/commands/build/build.js +++ b/packages/cli/src/commands/build/build.js @@ -22,13 +22,15 @@ import { outputDirectoryPath } from '../../utils/directories'; async function build(options) { const context = await createContext(options); await getBuildScript(context); + const outputDirectory = path.resolve(context.baseDirectory, outputDirectoryPath); context.print.info('Starting build.'); await context.buildScript({ logger: context.print, cacheDirectory: context.cacheDirectory, configDirectory: context.baseDirectory, - outputDirectory: path.resolve(context.baseDirectory, outputDirectoryPath), + outputDirectory, }); + context.print.info(`Build artifacts saved at ${outputDirectory}.`); } export default build; diff --git a/packages/cli/src/commands/cleanCache/cleanCache.test.js b/packages/cli/src/commands/cleanCache/cleanCache.test.js new file mode 100644 index 000000000..69babb06c --- /dev/null +++ b/packages/cli/src/commands/cleanCache/cleanCache.test.js @@ -0,0 +1,57 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import path from 'path'; +import { cleanDirectory } from '@lowdefy/node-utils'; +import cleanCache from './cleanCache'; +import createPrint from '../../utils/print'; + +jest.mock('@lowdefy/node-utils', () => { + const cleanDirectory = jest.fn(); + return { cleanDirectory }; +}); + +jest.mock('../../utils/print', () => { + const info = jest.fn(); + return () => ({ + info, + }); +}); + +const print = createPrint(); + +beforeEach(() => { + cleanDirectory.mockReset(); +}); + +test('cleanCache', async () => { + await cleanCache({}); + const cachePath = path.resolve(process.cwd(), './.lowdefy/.cache'); + expect(cleanDirectory.mock.calls).toEqual([[cachePath]]); + expect(print.info.mock.calls).toEqual([ + [`Cleaning cache at "${cachePath}".`], + ['Cache cleaned.'], + ]); +}); + +test('cleanCache baseDir', async () => { + await cleanCache({ baseDirectory: 'baseDir' }); + const cachePath = path.resolve(process.cwd(), 'baseDir/.lowdefy/.cache'); + expect(cleanDirectory.mock.calls).toEqual([[cachePath]]); + expect(print.info.mock.calls).toEqual([ + [`Cleaning cache at "${cachePath}".`], + ['Cache cleaned.'], + ]); +}); diff --git a/packages/cli/src/commands/dev/shell/index.html b/packages/cli/src/commands/dev/shell/index.html index 08d78fc43..1252121c3 100644 --- a/packages/cli/src/commands/dev/shell/index.html +++ b/packages/cli/src/commands/dev/shell/index.html @@ -1,3 +1,18 @@ + + diff --git a/packages/cli/src/utils/BatchChanges.test.js b/packages/cli/src/utils/BatchChanges.test.js new file mode 100644 index 000000000..88873aebc --- /dev/null +++ b/packages/cli/src/utils/BatchChanges.test.js @@ -0,0 +1,188 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import BatchChanges from './BatchChanges'; + +async function wait(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +const context = {}; + +test('BatchChanges calls the provided sync function', async () => { + const fn = jest.fn(); + const batchChanges = new BatchChanges({ fn, context }); + batchChanges.newChange(); + await wait(600); + expect(fn).toHaveBeenCalledTimes(1); +}); + +test('BatchChanges calls the provided async function', async () => { + let done = false; + const fn = jest.fn(async () => { + await wait(100); + done = true; + }); + const batchChanges = new BatchChanges({ fn, context }); + batchChanges.newChange(); + await wait(550); + expect(fn).toHaveBeenCalledTimes(1); + expect(done).toBe(false); + await wait(70); + expect(done).toBe(true); +}); + +test('BatchChanges calls the provided sync function only once if newChange is called multiple times', async () => { + const fn = jest.fn(); + const batchChanges = new BatchChanges({ fn, context }); + batchChanges.newChange(); + batchChanges.newChange(); + batchChanges.newChange(); + await wait(600); + expect(fn).toHaveBeenCalledTimes(1); +}); + +test('BatchChanges has a default minDelay', async () => { + const fn = jest.fn(); + const batchChanges = new BatchChanges({ fn, context }); + expect(batchChanges.minDelay).toBe(500); + expect(batchChanges.delay).toBe(500); +}); + +test('BatchChanges set minDelay', async () => { + const fn = jest.fn(); + const batchChanges = new BatchChanges({ fn, context, minDelay: 42 }); + expect(batchChanges.minDelay).toBe(42); + expect(batchChanges.delay).toBe(42); +}); + +test('BatchChanges resets timer if newChange is called multiple times in delay window', async () => { + const fn = jest.fn(); + const batchChanges = new BatchChanges({ fn, context }); + batchChanges.newChange(); + await wait(400); + batchChanges.newChange(); + await wait(400); + batchChanges.newChange(); + await wait(600); + expect(fn).toHaveBeenCalledTimes(1); +}); + +test('BatchChanges retries on errors, with back-off', async () => { + let count = 0; + let success = false; + const context = { + print: { + error: jest.fn(), + warn: jest.fn(), + }, + }; + const fn = jest.fn(() => { + if (count > 1) { + success = true; + return; + } + count += 1; + throw new Error(`Error: ${count}`); + }); + const batchChanges = new BatchChanges({ fn, context, minDelay: 100 }); + batchChanges.newChange(); + await wait(120); + expect(fn).toHaveBeenCalledTimes(1); + expect(context.print.error.mock.calls).toEqual([ + [ + 'Error: 1', + { + timestamp: true, + }, + ], + ]); + expect(context.print.warn.mock.calls).toEqual([ + [ + 'Retrying in 0.2s.', + { + timestamp: true, + }, + ], + ]); + expect(batchChanges.delay).toBe(200); + expect(count).toBe(1); + await wait(200); + expect(fn).toHaveBeenCalledTimes(2); + expect(context.print.error.mock.calls).toEqual([ + [ + 'Error: 1', + { + timestamp: true, + }, + ], + [ + 'Error: 2', + { + timestamp: true, + }, + ], + ]); + expect(context.print.warn.mock.calls).toEqual([ + [ + 'Retrying in 0.2s.', + { + timestamp: true, + }, + ], + [ + 'Retrying in 0.4s.', + { + timestamp: true, + }, + ], + ]); + expect(batchChanges.delay).toBe(400); + expect(count).toBe(2); + await wait(400); + expect(fn).toHaveBeenCalledTimes(3); + expect(context.print.error.mock.calls).toEqual([ + [ + 'Error: 1', + { + timestamp: true, + }, + ], + [ + 'Error: 2', + { + timestamp: true, + }, + ], + ]); + expect(context.print.warn.mock.calls).toEqual([ + [ + 'Retrying in 0.2s.', + { + timestamp: true, + }, + ], + [ + 'Retrying in 0.4s.', + { + timestamp: true, + }, + ], + ]); + expect(success).toBe(true); +}); diff --git a/packages/cli/src/utils/context.js b/packages/cli/src/utils/context.js index f2cd10d8b..b7185c343 100644 --- a/packages/cli/src/utils/context.js +++ b/packages/cli/src/utils/context.js @@ -19,7 +19,7 @@ import getLowdefyVersion from './getLowdefyVersion'; import createPrint from './print'; import { cacheDirectoryPath } from './directories'; -async function createContext(options) { +async function createContext(options = {}) { const context = { baseDirectory: path.resolve(options.baseDirectory || process.cwd()), print: createPrint({ timestamp: true }), diff --git a/packages/cli/src/utils/context.test.js b/packages/cli/src/utils/context.test.js new file mode 100644 index 000000000..020ccf35d --- /dev/null +++ b/packages/cli/src/utils/context.test.js @@ -0,0 +1,55 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import path from 'path'; +import createContext from './context'; +// eslint-disable-next-line no-unused-vars +import getLowdefyVersion from './getLowdefyVersion'; +// eslint-disable-next-line no-unused-vars +import createPrint from './print'; + +jest.mock('./getLowdefyVersion', () => async () => Promise.resolve('lowdefy-version')); +jest.mock('./print', () => () => 'print'); + +test('createContext, options undefined', async () => { + const context = await createContext(); + expect(context).toEqual({ + baseDirectory: path.resolve(process.cwd()), + cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), + version: 'lowdefy-version', + print: 'print', + }); +}); + +test('createContext, options empty', async () => { + const context = await createContext({}); + expect(context).toEqual({ + baseDirectory: path.resolve(process.cwd()), + cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), + version: 'lowdefy-version', + print: 'print', + }); +}); + +test('createContext, options baseDir', async () => { + const context = await createContext({ baseDirectory: 'baseDir' }); + expect(context).toEqual({ + baseDirectory: path.resolve(process.cwd(), 'baseDir'), + cacheDirectory: path.resolve(process.cwd(), 'baseDir/.lowdefy/.cache'), + version: 'lowdefy-version', + print: 'print', + }); +}); diff --git a/packages/cli/src/utils/directories.js b/packages/cli/src/utils/directories.js index ad6820e35..76c127959 100644 --- a/packages/cli/src/utils/directories.js +++ b/packages/cli/src/utils/directories.js @@ -1,3 +1,19 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + const cacheDirectoryPath = './.lowdefy/.cache'; const outputDirectoryPath = './.lowdefy/build'; diff --git a/packages/cli/src/utils/directories.test.js b/packages/cli/src/utils/directories.test.js new file mode 100644 index 000000000..772204cd5 --- /dev/null +++ b/packages/cli/src/utils/directories.test.js @@ -0,0 +1,24 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import * as directories from './directories'; + +test('directories', () => { + expect(directories).toEqual({ + cacheDirectoryPath: './.lowdefy/.cache', + outputDirectoryPath: './.lowdefy/build', + }); +}); diff --git a/packages/cli/src/utils/errorHandler.js b/packages/cli/src/utils/errorHandler.js index c604ef3f3..88a686934 100644 --- a/packages/cli/src/utils/errorHandler.js +++ b/packages/cli/src/utils/errorHandler.js @@ -24,6 +24,7 @@ function errorHandler(fn, options = {}) { } catch (error) { const print = createPrint(); print.error(error.message); + // TODO: Stay alive feature } } return run; diff --git a/packages/cli/src/utils/errorHandler.test.js b/packages/cli/src/utils/errorHandler.test.js index 2b7736ba3..77396a20d 100644 --- a/packages/cli/src/utils/errorHandler.test.js +++ b/packages/cli/src/utils/errorHandler.test.js @@ -62,33 +62,7 @@ test('Pass args to synchronous function', async () => { expect(res).toEqual({ arg1: '1', arg2: '2' }); }); -test('Catch error synchronous function, stay alive', async () => { - const fn = jest.fn(() => { - throw new Error('Error'); - }); - const wrapped = errorHandler(fn, { stayAlive: true }); - const res = await wrapped(); - expect(res).toBe(undefined); - expect(fn).toHaveBeenCalled(); - expect(print.error.mock.calls).toEqual([['Error']]); -}); - -test('Catch error asynchronous function, stay alive', async () => { - const fn = jest.fn(async () => { - await wait(3); - throw new Error('Async Error'); - }); - const wrapped = errorHandler(fn, { stayAlive: true }); - const res = await wrapped(); - expect(res).toBe(undefined); - expect(fn).toHaveBeenCalled(); - expect(print.error.mock.calls).toEqual([['Async Error']]); -}); - -test('Catch error synchronous function, exit process', async () => { - const realExit = process.exit; - const mockExit = jest.fn(); - process.exit = mockExit; +test('Catch error synchronous function', async () => { const fn = jest.fn(() => { throw new Error('Error'); }); @@ -96,14 +70,9 @@ test('Catch error synchronous function, exit process', async () => { await wrapped(); expect(fn).toHaveBeenCalled(); expect(print.error.mock.calls).toEqual([['Error']]); - expect(mockExit).toHaveBeenCalled(); - process.exit = realExit; }); -test('Catch error asynchronous function, exit process', async () => { - const realExit = process.exit; - const mockExit = jest.fn(); - process.exit = mockExit; +test('Catch error asynchronous function', async () => { const fn = jest.fn(async () => { await wait(3); throw new Error('Async Error'); @@ -112,6 +81,27 @@ test('Catch error asynchronous function, exit process', async () => { await wrapped(); expect(fn).toHaveBeenCalled(); expect(print.error.mock.calls).toEqual([['Async Error']]); - expect(mockExit).toHaveBeenCalled(); - process.exit = realExit; }); + +// test('Catch error synchronous function, stay alive', async () => { +// const fn = jest.fn(() => { +// throw new Error('Error'); +// }); +// const wrapped = errorHandler(fn, { stayAlive: true }); +// const res = await wrapped(); +// expect(res).toBe(undefined); +// expect(fn).toHaveBeenCalled(); +// expect(print.error.mock.calls).toEqual([['Error']]); +// }); + +// test('Catch error asynchronous function, stay alive', async () => { +// const fn = jest.fn(async () => { +// await wait(3); +// throw new Error('Async Error'); +// }); +// const wrapped = errorHandler(fn, { stayAlive: true }); +// const res = await wrapped(); +// expect(res).toBe(undefined); +// expect(fn).toHaveBeenCalled(); +// expect(print.error.mock.calls).toEqual([['Async Error']]); +// }); diff --git a/packages/cli/src/utils/getLowdefyVersion.js b/packages/cli/src/utils/getLowdefyVersion.js index 555b47d89..5233c3e28 100644 --- a/packages/cli/src/utils/getLowdefyVersion.js +++ b/packages/cli/src/utils/getLowdefyVersion.js @@ -19,7 +19,7 @@ import { type } from '@lowdefy/helpers'; import { readFile } from '@lowdefy/node-utils'; import YAML from 'js-yaml'; -async function getLowdefyVersion(context) { +async function getLowdefyVersion(context = {}) { const lowdefyYaml = await readFile( path.resolve(context.baseDirectory || process.cwd(), 'lowdefy.yaml') ); diff --git a/packages/cli/src/utils/getLowdefyVersion.test.js b/packages/cli/src/utils/getLowdefyVersion.test.js index 637524144..ca75f73a3 100644 --- a/packages/cli/src/utils/getLowdefyVersion.test.js +++ b/packages/cli/src/utils/getLowdefyVersion.test.js @@ -30,6 +30,19 @@ beforeEach(() => { }); test('get version from yaml file', async () => { + readFile.mockImplementation((filePath) => { + if (filePath === path.resolve(process.cwd(), 'lowdefy.yaml')) { + return ` + version: 1.0.0 + `; + } + return null; + }); + const version = await getLowdefyVersion({}); + expect(version).toEqual('1.0.0'); +}); + +test('get version from yaml file, context default value', async () => { readFile.mockImplementation((filePath) => { if (filePath === path.resolve(process.cwd(), 'lowdefy.yaml')) { return ` @@ -51,7 +64,7 @@ test('get version from yaml file, base dir specified', async () => { } return null; }); - const version = await getLowdefyVersion('./baseDir'); + const version = await getLowdefyVersion({ baseDirectory: './baseDir' }); expect(version).toEqual('1.0.0'); }); @@ -64,7 +77,7 @@ test('could not find lowdefy.yaml in cwd', async () => { version: 1.0.0 `; }); - await expect(getLowdefyVersion()).rejects.toThrow( + await expect(getLowdefyVersion({})).rejects.toThrow( 'Could not find "lowdefy.yaml" file in current working directory. Change directory to a Lowdefy project, or specify a base directory.' ); }); @@ -78,7 +91,7 @@ test('could not find lowdefy.yaml in base dir', async () => { version: 1.0.0 `; }); - await expect(getLowdefyVersion('./baseDir')).rejects.toThrow( + await expect(getLowdefyVersion({ baseDirectory: './baseDir' })).rejects.toThrow( 'Could not find "lowdefy.yaml" file in specified base directory' ); }); @@ -94,7 +107,7 @@ test('lowdefy.yaml is invalid yaml', async () => { } return null; }); - await expect(getLowdefyVersion()).rejects.toThrow( + await expect(getLowdefyVersion({})).rejects.toThrow( 'Could not parse "lowdefy.yaml" file. Received error ' ); }); @@ -110,7 +123,7 @@ test('No version specified', async () => { } return null; }); - await expect(getLowdefyVersion()).rejects.toThrow( + await expect(getLowdefyVersion({})).rejects.toThrow( 'No version specified in "lowdefy.yaml" file. Specify a version in the "version field".' ); }); @@ -124,7 +137,7 @@ test('Version is not a string', async () => { } return null; }); - await expect(getLowdefyVersion()).rejects.toThrow( + await expect(getLowdefyVersion({})).rejects.toThrow( 'Version number specified in "lowdefy.yaml" file is not valid. Received 1.' ); }); @@ -138,7 +151,7 @@ test('Version is not a valid version number', async () => { } return null; }); - await expect(getLowdefyVersion()).rejects.toThrow( + await expect(getLowdefyVersion({})).rejects.toThrow( 'Version number specified in "lowdefy.yaml" file is not valid. Received "v1-0-3".' ); }); diff --git a/packages/cli/src/utils/print.js b/packages/cli/src/utils/print.js index 8f248d674..1441f409c 100644 --- a/packages/cli/src/utils/print.js +++ b/packages/cli/src/utils/print.js @@ -19,7 +19,7 @@ import chalk from 'chalk'; const printToTerminal = (color, options = {}) => (text) => { let message; if (options.timestamp) { - const time = options.timestamp === true ? new Date(Date.now()) : new Date(options.timestamp); + const time = new Date(Date.now()); const h = time.getHours(); const m = time.getMinutes(); const s = time.getSeconds(); diff --git a/packages/cli/src/utils/print.test.js b/packages/cli/src/utils/print.test.js new file mode 100644 index 000000000..79db09e89 --- /dev/null +++ b/packages/cli/src/utils/print.test.js @@ -0,0 +1,153 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import createPrint from './print'; + +const mockConsoleLog = jest.fn(); +const mockGetHours = jest.fn(); +const mockGetMinutes = jest.fn(); +const mockGetSeconds = jest.fn(); +// const realLog = console.log; +// const realNow = Date.now; +console.log = mockConsoleLog; +// eslint-disable-next-line no-global-assign +Date = jest.fn(() => ({ + getHours: mockGetHours, + getMinutes: mockGetMinutes, + getSeconds: mockGetSeconds, +})); + +Date.now = () => {}; + +beforeEach(() => { + mockConsoleLog.mockReset(); + mockGetHours.mockReset(); + mockGetMinutes.mockReset(); + mockGetSeconds.mockReset(); +}); + +// afterAll(() => { +// console.log = realLog; +// Date.now = realNow; +// }); + +test('create print', () => { + const print = createPrint(); + expect(print).toMatchInlineSnapshot(` + Object { + "error": [Function], + "info": [Function], + "log": [Function], + "warn": [Function], + } + `); +}); + +test('print info', () => { + const print = createPrint(); + print.info('Test info'); + expect(mockConsoleLog.mock.calls).toEqual([['Test info']]); +}); + +test('print log', () => { + const print = createPrint(); + print.log('Test log'); + expect(mockConsoleLog.mock.calls).toEqual([['Test log']]); +}); + +test('print warn', () => { + const print = createPrint(); + print.warn('Test warn'); + expect(mockConsoleLog.mock.calls).toEqual([['Test warn']]); +}); + +test('print error', () => { + const print = createPrint(); + print.error('Test error'); + expect(mockConsoleLog.mock.calls).toEqual([['Test error']]); +}); + +test('print info with timestamp, less than 10', () => { + mockGetHours.mockImplementation(() => 1); + mockGetMinutes.mockImplementation(() => 2); + mockGetSeconds.mockImplementation(() => 3); + const print = createPrint({ timestamp: true }); + print.info('Test info'); + expect(mockConsoleLog.mock.calls).toEqual([['01:02:03 - Test info']]); +}); + +test('print log with timestamp, less than 10', () => { + mockGetHours.mockImplementation(() => 1); + mockGetMinutes.mockImplementation(() => 2); + mockGetSeconds.mockImplementation(() => 3); + const print = createPrint({ timestamp: true }); + print.log('Test log'); + expect(mockConsoleLog.mock.calls).toEqual([['01:02:03 - Test log']]); +}); + +test('print warn with timestamp, less than 10', () => { + mockGetHours.mockImplementation(() => 1); + mockGetMinutes.mockImplementation(() => 2); + mockGetSeconds.mockImplementation(() => 3); + const print = createPrint({ timestamp: true }); + print.warn('Test warn'); + expect(mockConsoleLog.mock.calls).toEqual([['01:02:03 - Test warn']]); +}); + +test('print error with timestamp, less than 10', () => { + mockGetHours.mockImplementation(() => 1); + mockGetMinutes.mockImplementation(() => 2); + mockGetSeconds.mockImplementation(() => 3); + const print = createPrint({ timestamp: true }); + print.error('Test error'); + expect(mockConsoleLog.mock.calls).toEqual([['01:02:03 - Test error']]); +}); + +test('print info with timestamp, two digits', () => { + mockGetHours.mockImplementation(() => 11); + mockGetMinutes.mockImplementation(() => 22); + mockGetSeconds.mockImplementation(() => 33); + const print = createPrint({ timestamp: true }); + print.info('Test info'); + expect(mockConsoleLog.mock.calls).toEqual([['11:22:33 - Test info']]); +}); + +test('print log with timestamp, two digits', () => { + mockGetHours.mockImplementation(() => 11); + mockGetMinutes.mockImplementation(() => 22); + mockGetSeconds.mockImplementation(() => 33); + const print = createPrint({ timestamp: true }); + print.log('Test log'); + expect(mockConsoleLog.mock.calls).toEqual([['11:22:33 - Test log']]); +}); + +test('print warn with timestamp, two digits', () => { + mockGetHours.mockImplementation(() => 11); + mockGetMinutes.mockImplementation(() => 22); + mockGetSeconds.mockImplementation(() => 33); + const print = createPrint({ timestamp: true }); + print.warn('Test warn'); + expect(mockConsoleLog.mock.calls).toEqual([['11:22:33 - Test warn']]); +}); + +test('print error with timestamp, two digits', () => { + mockGetHours.mockImplementation(() => 11); + mockGetMinutes.mockImplementation(() => 22); + mockGetSeconds.mockImplementation(() => 33); + const print = createPrint({ timestamp: true }); + print.error('Test error'); + expect(mockConsoleLog.mock.calls).toEqual([['11:22:33 - Test error']]); +});