From cfb5c36427b6c61aa9420e421948602c23ac48fa Mon Sep 17 00:00:00 2001 From: Benjamin Charity Date: Fri, 24 May 2019 08:52:55 -0400 Subject: [PATCH 1/2] feat(Utility): expose UUID generator ISSUES CLOSED: #91 --- ngx-tools/src/README.md | 13 ++++++++++++ ngx-tools/src/public-api.ts | 1 + ngx-tools/src/uuid/uuid.spec.ts | 37 +++++++++++++++++++++++++++++++++ ngx-tools/src/uuid/uuid.ts | 23 ++++++++++++++++++++ package.json | 3 ++- tools/jest-global-mocks.ts | 37 ++++++++++++++++----------------- tslint.spec.json | 1 + yarn.lock | 13 ++++++++---- 8 files changed, 104 insertions(+), 24 deletions(-) create mode 100644 ngx-tools/src/uuid/uuid.spec.ts create mode 100644 ngx-tools/src/uuid/uuid.ts diff --git a/ngx-tools/src/README.md b/ngx-tools/src/README.md index d919eaa9..2774562c 100644 --- a/ngx-tools/src/README.md +++ b/ngx-tools/src/README.md @@ -55,6 +55,7 @@ isArray([]); // Returns: true - [`isString`](#isstring) - [`arrayHasAllElementsSet`](#arrayhasallelementsset) - [`VERSION`](#version) +- [UUID](#uuid) @@ -885,6 +886,18 @@ VERSION.patch // Returns: 3 ``` +### UUID + +[[source]](uuid/uuid.ts) + +Generate a canonically formatted UUID that is Version 1 through 5 and is the appropriate Variant as per RFC4122. + +```typescript +import { generateUUID } from '@terminus/ngx-tools'; + +generateUUID(); // Returns a UUID such as: `f4ee5eed-ed19-3681-713e-907a23ed7858` +``` + diff --git a/ngx-tools/src/public-api.ts b/ngx-tools/src/public-api.ts index 0e2033c6..fef3e37f 100644 --- a/ngx-tools/src/public-api.ts +++ b/ngx-tools/src/public-api.ts @@ -32,3 +32,4 @@ export * from './update-control-on-input-changes/update-control-on-input-changes export * from './verify-types/index'; export * from './version/version'; export * from './window/window.service'; +export * from './uuid/uuid'; diff --git a/ngx-tools/src/uuid/uuid.spec.ts b/ngx-tools/src/uuid/uuid.spec.ts new file mode 100644 index 00000000..e4b98d9c --- /dev/null +++ b/ngx-tools/src/uuid/uuid.spec.ts @@ -0,0 +1,37 @@ +import { uuidRegex } from '@terminus/ngx-tools/regex'; + +import { generateUUID } from './uuid'; + + +describe(`uuid`, function() { + const testUUIDGenerator = function(generator: () => string, iterations = 1, done: jest.DoneCallback): Promise { + const uuidstore: Record = {}; + let i; + let newUuid; + + for (i = 0; i < iterations; i++) { + newUuid = generator(); + + // Test Validity + if (!uuidRegex.test(newUuid)) { + done.fail(new Error('This is the error')); + } + + // Test Collision + if (uuidstore[newUuid]) { + done.fail(new Error(`Collision on ${newUuid}`)); + } + uuidstore[newUuid] = newUuid; + } + + return Promise.resolve(); + }; + + + test(`should create UUIDs that do not collide`, function(done) { + testUUIDGenerator(generateUUID, 100, done).then(() => { + done(); + }); + }); + +}); diff --git a/ngx-tools/src/uuid/uuid.ts b/ngx-tools/src/uuid/uuid.ts new file mode 100644 index 00000000..cdd2eb62 --- /dev/null +++ b/ngx-tools/src/uuid/uuid.ts @@ -0,0 +1,23 @@ +/* eslint-disable no-magic-numbers */ +/** + * Generate a canonically formatted UUID that is Version 1 through 5 and is the appropriate Variant as per RFC4122. + * + * @example + * generateUUID() // Returns: `f4ee5eed-ed19-3681-713e-907a23ed7858` + * + * @return The UUID + */ +export function generateUUID(): string { + const buf = new Uint16Array(8); + window.crypto.getRandomValues(buf); + + const S4 = function(num: number) { + let ret = num.toString(16); + while (ret.length < 4) { + ret = `0${ret}`; + } + return ret; + }; + + return (`${S4(buf[0]) + S4(buf[1])}-${S4(buf[2])}-${S4(buf[3])}-${S4(buf[4])}-${S4(buf[5])}${S4(buf[6])}${S4(buf[7])}`); +} diff --git a/package.json b/package.json index 77d4a924..744a616a 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,8 @@ "tsickle": "^0.34.0", "tslint": "^5.16.0", "typescript": "~3.2.1", - "validate-commit-msg": "^2.14.0" + "validate-commit-msg": "^2.14.0", + "window-crypto": "^1.1.0" }, "husky": { "hooks": { diff --git a/tools/jest-global-mocks.ts b/tools/jest-global-mocks.ts index 78a44911..854ba0f3 100644 --- a/tools/jest-global-mocks.ts +++ b/tools/jest-global-mocks.ts @@ -1,42 +1,42 @@ +// Polyfill `window.crypto` +import 'window-crypto'; + // tslint:disable no-any no-unsafe-any const mock = () => { let storage: Record = {}; return { - getItem: (key: string): any => key in storage ? storage[key] : null, + getItem: (key: string): any => (key in storage ? storage[key] : null), setItem: (key: string, value: any) => { storage[key] = value || ''; return storage[key]; }, removeItem: (key: string) => delete storage[key], - clear: () => storage = {}, + clear: () => (storage = {}), }; }; // tslint:enable no-any no-unsafe-any Object.defineProperty(window, 'localStorage', {value: mock()}); Object.defineProperty(window, 'sessionStorage', {value: mock()}); -Object.defineProperty(window, 'getComputedStyle', { - value: () => ['-webkit-appearance'], -}); +Object.defineProperty(window, 'getComputedStyle', {value: () => ['-webkit-appearance']}); Object.defineProperty(window, 'CSS', {value: () => ({})}); - - /** * Patches for Material */ const WARN_SUPPRESSING_PATTERNS = [ -/Could not find Angular Material core theme/, -/Could not find HammerJS/, + /Could not find Angular Material core theme/, + /Could not find HammerJS/, ]; +// eslint-disable-next-line no-console const warn = console.warn; Object.defineProperty(console, 'warn', { - value: (...params: string[]) => { - if (!WARN_SUPPRESSING_PATTERNS.some((pattern) => pattern.test(params[0]))) { - warn(...params); - } - }, + value: (...params: string[]) => { + if (!WARN_SUPPRESSING_PATTERNS.some(pattern => pattern.test(params[0]))) { + warn(...params); + } + }, }); Object.defineProperty(window, 'matchMedia', { value: () => ( @@ -48,9 +48,8 @@ Object.defineProperty(window, 'matchMedia', { ), }); Object.defineProperty(document.body.style, 'transform', { - value: () => - ({ - enumerable: true, - configurable: true, - }), + value: () => ({ + enumerable: true, + configurable: true, + }), }); diff --git a/tslint.spec.json b/tslint.spec.json index 5e29a64f..4bb2d974 100644 --- a/tslint.spec.json +++ b/tslint.spec.json @@ -2,6 +2,7 @@ "extends": "@terminus/tslint-config-frontend/testing", "rules" : { "no-implicit-dependencies": false, + "no-non-null-assertion": false, "prefer-on-push-component-change-detection": false } } diff --git a/yarn.lock b/yarn.lock index 2f101753..35e57cc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -474,10 +474,10 @@ "@typescript-eslint/eslint-plugin-tslint" "^1.4.2" "@typescript-eslint/parser" "^1.4.2" -"@terminus/ngx-tools@*": - version "6.7.0" - resolved "https://registry.yarnpkg.com/@terminus/ngx-tools/-/ngx-tools-6.7.0.tgz#6a5bcc41b9d8802669f3e030d3de835da2d77a17" - integrity sha512-bytDD9w6Rl6WilBRfvUwnp76HIB/U6LKYcycHJRlA3Fs98QRkSXgyPuh3uhN/yGxBB67JEGs78w9Uuxe79dcXg== +"@terminus/ngx-tools@^6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@terminus/ngx-tools/-/ngx-tools-6.10.0.tgz#d19da4e78ca489c189c8576f9db981cb0f7ce735" + integrity sha512-qxXupnVfj84gxSzk2AV/GvpS2EmGaA78mNiMlwhaY3s/CwB/3Bfe1+zCrhVBRzgyPUk0VDRcK3OSGng/efPHUQ== dependencies: tslib "^1.9.0" @@ -12847,6 +12847,11 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" +window-crypto@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/window-crypto/-/window-crypto-1.1.0.tgz#1f4acfe4ddacabfb5b4bd6eda7d2fe63e4e0b288" + integrity sha512-KD5ucryy9RGf9Yb68ynEFTXv1DVxAjqn9FmTlFTW+G38rbMmkJ92+iEEmwMoNZShdy9K6PT6s3uE7imyiXcFWA== + windows-release@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" From 3298a8ab19c6bfb35ef0c0fabebaf89d39676e71 Mon Sep 17 00:00:00 2001 From: Benjamin Charity Date: Fri, 24 May 2019 09:23:53 -0400 Subject: [PATCH 2/2] style: fix lint violations --- ngx-tools/src/jwt-token-managment/effects.spec.ts | 4 ++++ .../jwt-token-managment/utilities/token-escalator.spec.ts | 5 +++++ ngx-tools/src/retry-with-backoff/retry-with-backoff.spec.ts | 3 +++ ngx-tools/src/uuid/uuid.spec.ts | 2 +- ngx-tools/testing/src/utilities/event-objects.spec.ts | 1 - ngx-tools/testing/src/utilities/event-objects.ts | 1 - 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ngx-tools/src/jwt-token-managment/effects.spec.ts b/ngx-tools/src/jwt-token-managment/effects.spec.ts index 752f9c14..cedaf460 100644 --- a/ngx-tools/src/jwt-token-managment/effects.spec.ts +++ b/ngx-tools/src/jwt-token-managment/effects.spec.ts @@ -96,6 +96,7 @@ describe(`JWT Token Effects`, function() { }; test(`should provide a cookie and store cookie message if the cookie is set`, () => { + // tslint:disable-next-line deprecation const currentState = of(blankState); mockCookieService.get.mockReturnValue('abcd'); @@ -114,6 +115,7 @@ describe(`JWT Token Effects`, function() { }); test(`should emit nothing if state is loaded`, () => { + // tslint:disable-next-line deprecation const currentState = of({ ...blankState, jwtTokens: { @@ -131,6 +133,7 @@ describe(`JWT Token Effects`, function() { }); test(`should emit nothing if state is empty`, () => { + // tslint:disable-next-line deprecation const currentState = of({ ...blankState, jwtTokens: { @@ -149,6 +152,7 @@ describe(`JWT Token Effects`, function() { }); it(`it should only announce the initial if the cookie is empty`, () => { + // tslint:disable-next-line deprecation const currentState = of(blankState); mockCookieService.get.mockReturnValue(''); diff --git a/ngx-tools/src/jwt-token-managment/utilities/token-escalator.spec.ts b/ngx-tools/src/jwt-token-managment/utilities/token-escalator.spec.ts index b417b1ac..313e68d1 100644 --- a/ngx-tools/src/jwt-token-managment/utilities/token-escalator.spec.ts +++ b/ngx-tools/src/jwt-token-managment/utilities/token-escalator.spec.ts @@ -25,6 +25,7 @@ describe(`TokenEscalator`, function() { let escalator: TokenEscalator; let actions: Observable; const tokenName = 'foo'; + // tslint:disable-next-line deprecation const authorizeUrl = of('/foobar'); beforeEach(() => { @@ -56,7 +57,9 @@ describe(`TokenEscalator`, function() { test(`should dispatch success on a successful response`, () => { actions = cold('a', {a: new JwtActions.EscalateToken(tokenName)}); const responseBody = {token: 'asdfkjlslfd'}; + // tslint:disable-next-line deprecation mockHttp.get.mockReturnValue(of(responseBody)); + // tslint:disable-next-line deprecation mockStore.select.mockReturnValue(of('currentToken')); ( @@ -74,7 +77,9 @@ describe(`TokenEscalator`, function() { test(`should dispatch failed if the token fails to extract`, () => { actions = cold('a', {a: new JwtActions.EscalateToken(tokenName)}); const responseBody = {}; + // tslint:disable-next-line deprecation mockHttp.get.mockReturnValue(of(responseBody)); + // tslint:disable-next-line deprecation mockStore.select.mockReturnValue(of('currentToken')); ( diff --git a/ngx-tools/src/retry-with-backoff/retry-with-backoff.spec.ts b/ngx-tools/src/retry-with-backoff/retry-with-backoff.spec.ts index 721e3ce9..a39b6cab 100644 --- a/ngx-tools/src/retry-with-backoff/retry-with-backoff.spec.ts +++ b/ngx-tools/src/retry-with-backoff/retry-with-backoff.spec.ts @@ -14,6 +14,7 @@ describe(`retryWithBackoff`, function() { const error = new Error('bar'); const seenValues: {[idx: number]: number} = {}; + // tslint:disable-next-line deprecation of(1, 2, 3).pipe( tap(v => { if (!seenValues[v]) { @@ -33,6 +34,7 @@ describe(`retryWithBackoff`, function() { retries: 2, delayCalculator: linearBackoff, }), + // tslint:disable-next-line no-any ).subscribe(() => {}, (err: any) => { expect(err).toEqual(error); expect(seenValues).toEqual({ @@ -48,6 +50,7 @@ describe(`retryWithBackoff`, function() { const error = new Error('bar'); const seenValues: {[idx: number]: number} = {}; + // tslint:disable-next-line deprecation of(1, 2, 3, 4).pipe( tap(v => { if (!seenValues[v]) { diff --git a/ngx-tools/src/uuid/uuid.spec.ts b/ngx-tools/src/uuid/uuid.spec.ts index e4b98d9c..e48d1bc5 100644 --- a/ngx-tools/src/uuid/uuid.spec.ts +++ b/ngx-tools/src/uuid/uuid.spec.ts @@ -14,7 +14,7 @@ describe(`uuid`, function() { // Test Validity if (!uuidRegex.test(newUuid)) { - done.fail(new Error('This is the error')); + done.fail(new Error(`Not a valid UUID: ${newUuid}`)); } // Test Collision diff --git a/ngx-tools/testing/src/utilities/event-objects.spec.ts b/ngx-tools/testing/src/utilities/event-objects.spec.ts index 71e38f9e..8a0ea40d 100644 --- a/ngx-tools/testing/src/utilities/event-objects.spec.ts +++ b/ngx-tools/testing/src/utilities/event-objects.spec.ts @@ -50,7 +50,6 @@ describe(`event-objects`, function() { const actual: KeyboardEvent = createKeyboardEvent('keydown', KEYS.ENTER, target); expect(actual.code).toEqual(KEYS.ENTER.code); expect(actual.key).toEqual(KEYS.ENTER.code); - expect(actual.keyCode).toEqual(KEYS.ENTER.keyCode); expect(actual.target).toEqual(target); }); diff --git a/ngx-tools/testing/src/utilities/event-objects.ts b/ngx-tools/testing/src/utilities/event-objects.ts index 460bdc34..6071c61b 100644 --- a/ngx-tools/testing/src/utilities/event-objects.ts +++ b/ngx-tools/testing/src/utilities/event-objects.ts @@ -96,7 +96,6 @@ export function createKeyboardEvent( // Webkit Browsers don't set the keyCode when calling the init function. // See related bug https://bugs.webkit.org/show_bug.cgi?id=16735 Object.defineProperties(event, { - keyCode: {get: () => key.keyCode}, key: {get: () => key.code}, target: {get: () => target}, code: {get: () => key.code},