From 715c38db7ac68e7b9cdfe2c560d8bf6ed72b9963 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Tue, 24 Nov 2020 15:52:35 +0100 Subject: [PATCH 01/14] build: switch build system to microbundle & improve tooling - Microbundle with UMD, CJS & ESM builds - CI/CD powered by semantic-release - Test setup with Jest - Prettier & eslint setup --- .github/workflows/ci-cd.yaml | 58 + .travis.yml | 20 - code-of-conduct.md | 74 - examples/basic.html | 2 +- microbundle.config.js | 42 + package-lock.json | 35274 +++++----------- package.json | 255 +- rollup.config.ts | 69 - src/alma-widgets.ts | 29 - src/index.ts | 31 + src/types.ts | 12 +- src/utils/index.ts | 111 +- src/utils/polyfills.js | 507 +- src/widgets/base.ts | 71 +- src/widgets/how_it_works/default_templates.ts | 240 +- src/widgets/how_it_works/index.ts | 130 +- src/widgets/how_it_works/types.ts | 92 +- src/widgets/payment_plan/default_templates.ts | 186 +- src/widgets/payment_plan/index.ts | 165 +- src/widgets/payment_plan/types.ts | 64 +- src/widgets/types.ts | 8 +- src/widgets_controller.ts | 28 +- test/alma-widgets.test.ts | 10 +- tools/gh-pages-publish.ts | 31 - tools/semantic-release-prepare.ts | 54 - tsconfig.json | 10 +- tslint.json | 6 - 27 files changed, 10982 insertions(+), 26597 deletions(-) create mode 100644 .github/workflows/ci-cd.yaml delete mode 100644 .travis.yml delete mode 100644 code-of-conduct.md create mode 100644 microbundle.config.js delete mode 100644 rollup.config.ts delete mode 100644 src/alma-widgets.ts create mode 100644 src/index.ts delete mode 100644 tools/gh-pages-publish.ts delete mode 100644 tools/semantic-release-prepare.ts delete mode 100644 tslint.json diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml new file mode 100644 index 0000000..94c4d69 --- /dev/null +++ b/.github/workflows/ci-cd.yaml @@ -0,0 +1,58 @@ +name: CI/CD + +on: + pull_request: + push: + branches: + - master + +jobs: + tests: + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + node: [10, 12, 13, 14] + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Setup Node.js ${{ matrix.node }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm run test + - name: Upload coverage data + uses: codecov/codecov-action@v1 + + release: + name: Release + needs: tests + if: "github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # Do not persist the GH token, so that the release step uses its own credentials. + persist-credentials: false + - name: Setup Node.js + uses: actions/setup-node@v1 + with: + node-version: 14 + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }} + GIT_AUTHOR_NAME: github-actions + GIT_AUTHOR_EMAIL: 44210433+github-actions@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions + GIT_COMMITTER_EMAIL: 44210433+github-actions@users.noreply.github.com + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx semantic-release diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c2e8e10..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: node_js -cache: - directories: - - ~/.npm -notifications: - email: false -node_js: - - '10' - - '11' - - '8' - - '6' -script: - - npm run test:prod && npm run build -after_success: - - npm run travis-deploy-once "npm run report-coverage" - - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run deploy-docs"; fi - - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run semantic-release"; fi -branches: - except: - - /^v\d+\.\d+\.\d+$/ diff --git a/code-of-conduct.md b/code-of-conduct.md deleted file mode 100644 index 4a9568b..0000000 --- a/code-of-conduct.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at alexjovermorales@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/examples/basic.html b/examples/basic.html index df91a46..8f2fabd 100644 --- a/examples/basic.html +++ b/examples/basic.html @@ -4,7 +4,7 @@ Alma Widgets basic example - + - - -

Échéancier

+ + + + +

Échéancier

- + -
-







-
+
+







+
- - + - + widgets.render() + })() + + diff --git a/src/global.d.ts b/src/global.d.ts index a798d89..ff2423e 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,4 @@ -declare module "*.svg" { - const value: string; - export default value; +declare module '*.svg' { + const value: string + export default value } diff --git a/src/widgets/how_it_works/styles.scss b/src/widgets/how_it_works/styles.scss index 3f702c4..9f120fe 100644 --- a/src/widgets/how_it_works/styles.scss +++ b/src/widgets/how_it_works/styles.scss @@ -22,7 +22,6 @@ } } - .alma-modal--wrapper { position: fixed; @@ -63,7 +62,6 @@ } } - .alma-modal--close-btn { position: absolute; right: 10px; @@ -129,23 +127,24 @@ display: inline-block; padding: 10px; margin: 0 10px; - border: 2px solid #DADFFB; + border: 2px solid #dadffb; border-radius: 3px; cursor: pointer; color: #3651d2; transition: all 300ms ease-in-out; - &:hover, &.alma-hiw_content--plan-btn__selected { + &:hover, + &.alma-hiw_content--plan-btn__selected { border-color: #3651d2; - background: #F0F8FF; + background: #f0f8ff; } .alma-hiw_content--plan-btn--installments_count { position: relative; border-bottom: 3px solid #3651d2; - font-family: "Helvetica Neue", Helvetica, Arial, "Segoe UI", sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, 'Segoe UI', sans-serif; font-weight: bold; } } @@ -176,7 +175,8 @@ font-size: 0.8em; transition: all 300ms ease-in-out; - &:hover, &:focus { + &:hover, + &:focus { background-color: #3651d2; color: white; outline: none; diff --git a/src/widgets/index.ts b/src/widgets/index.ts index 3d4609d..bacaf00 100644 --- a/src/widgets/index.ts +++ b/src/widgets/index.ts @@ -1,2 +1,2 @@ -export {PaymentPlanWidget as PaymentPlan} from './payment_plan'; -export {HowItWorksWidget as HowItWorks} from './how_it_works'; +export { PaymentPlanWidget as PaymentPlan } from './payment_plan' +export { HowItWorksWidget as HowItWorks } from './how_it_works' diff --git a/src/widgets/payment_plan/styles.scss b/src/widgets/payment_plan/styles.scss index e516f5d..af93883 100644 --- a/src/widgets/payment_plan/styles.scss +++ b/src/widgets/payment_plan/styles.scss @@ -15,7 +15,7 @@ vertical-align: middle; border-bottom: 3px solid #3651d2; - font-family: "Helvetica Neue", Helvetica, Arial, "Segoe UI", sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, 'Segoe UI', sans-serif; font-weight: bold; -webkit-font-smoothing: antialiased; diff --git a/tsconfig.json b/tsconfig.json index 4be5197..fd402ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "moduleResolution": "node", "target": "ESNext", - "module":"ESNext", + "module": "ESNext", "lib": ["es2015", "es2016", "es2017", "dom"], "strict": true, "sourceMap": true, @@ -12,15 +12,11 @@ "emitDecoratorMetadata": true, "declarationDir": "dist/types", "outDir": "dist/lib", - "typeRoots": [ - "node_modules/@types" - ], + "typeRoots": ["node_modules/@types"], "baseUrl": ".", "paths": { "@/*": ["src/*"] } }, - "include": [ - "src" - ] + "include": ["src"] } From 1311e3b6499880420caed00c17b0f57cabae707a Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 13:07:11 +0100 Subject: [PATCH 03/14] refactor(types): rewrote types to be more solid and compile without errors They still aren't perfect, but already much better. Runtime errors are potentially still possible in some cases --- package-lock.json | 18 ++- package.json | 15 ++- src/types.ts | 115 +++++++++++++++++ src/widgets/base.ts | 86 +++++++------ src/widgets/config.ts | 82 ++++++++++++ src/widgets/how_it_works/default_templates.ts | 16 +-- src/widgets/how_it_works/index.ts | 121 ++++++++---------- src/widgets/how_it_works/types.ts | 29 ++--- src/widgets/payment_plan/default_templates.ts | 34 ++--- src/widgets/payment_plan/index.ts | 72 +++++------ src/widgets/payment_plan/types.ts | 54 ++++---- src/widgets/types.ts | 4 +- src/widgets_controller.ts | 13 +- 13 files changed, 428 insertions(+), 231 deletions(-) create mode 100644 src/widgets/config.ts diff --git a/package-lock.json b/package-lock.json index 1a6d2a8..5b5f8c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2508,6 +2508,21 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/lodash": { + "version": "4.14.165", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz", + "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==", + "dev": true + }, + "@types/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-8G41YFhmOl8Ck6NrwLK5hhnbz6ADfuDJP+zusDnX3PoYhfC60+H/rQE6zmdO4yFzPCPJPY4oGZK2spbXm6gYEA==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", @@ -8332,8 +8347,7 @@ "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.isstring": { "version": "4.0.1", diff --git a/package.json b/package.json index a4dda11..4eecbf2 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@semantic-release/changelog": "^5.0.1", "@semantic-release/git": "^9.0.0", "@types/jest": "^26.0.14", + "@types/lodash.isplainobject": "^4.0.6", "@types/node": "^14.11.10", "@typescript-eslint/eslint-plugin": "^4.4.1", "@typescript-eslint/parser": "^4.4.1", @@ -65,7 +66,8 @@ "typescript": "^4.0.3" }, "dependencies": { - "@alma/client": "^1.0.0" + "@alma/client": "^1.0.0", + "lodash.isplainobject": "^4.0.6" }, "commitlint": { "extends": [ @@ -102,7 +104,16 @@ ], "plugins": [ "@typescript-eslint" - ] + ], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "varsIgnorePattern": "^_+$", + "argsIgnorePattern": "^_+$" + } + ] + } }, { "files": [ diff --git a/src/types.ts b/src/types.ts index eb415c8..f0cc0fa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,121 @@ // For semantic purposes +import { AnyArray, Builtin } from 'ts-essentials' + export type integer = number export { DeepRequired } from 'ts-essentials' export type DOMContent = string | HTMLElement | HTMLElement[] + +export interface IObject { + [key: string]: unknown +} + +export type IsObject = T extends AnyArray + ? false + : // eslint-disable-next-line @typescript-eslint/ban-types + T extends object + ? true + : false + +/* + This type can be used as a "recursion stopper" in recursive helper types. + It's useful to prevent "mangling" types with DeepRequired or Deep*, that would recurse into any + type used within `T`. + + Here's how to use it: + + ``` + interface Test { + prop1?: string, + prop2?: { + prop3?: Preserve + }, + prop4: number + } + ``` + + Using a "Preserve-aware" recursive type allows casting `Preserve` back to + `SomeTypeToPreserve` instead of recursing over all its properties. + i.e. `DeepRequired` gives: + + ``` + interface Test { + ... + prop2: { + prop3: DeepRequired // <- "mangles" SomeTypeToPreserve + } + ... + } + ``` + + ... while `PreservedDeepRequired` gives: + + ``` + interface Test { + ... + prop2: { + prop3: SomeTypeToPreserve + } + ... + } + ``` + */ +export type Preserve = T & { readonly __preserve__: 'preserve' } + +/* + Transforms `T` into an identical shape, but with all `Preserve

` types mapped back to `P`: + + ``` + ResolvePreserve> == T + ``` + + It can be useful to make an interface more dev-friendly. Consider this: + + ``` + interface Test { + className?: string + container?: Preserve + } + ``` + + Because `container` uses `Preserve`, its type is actually: + + ``` + HTMLElement & { readonly __preserve__: 'preserve' } + ``` + + Since this is not assignable to `HTMLElement`, using `Test` directly requires using an explicit + casting for all its "preserved" properties, which is cumbersome: + + ``` + declare const test: Test + const container: HTMLElement = test.container as HTMLElement + ``` + + Using `ResolvePreserve` + */ +export type ResolvePreserve = NonNullable extends Preserve + ? P + : { + [K in keyof T]: NonNullable extends Builtin + ? T[K] + : NonNullable extends AnyArray + ? T[K] + : IsObject> extends true + ? ResolvePreserve + : T[K] + } + +// Just like DeepRequired, but will not recurse into `Preserve

` types: will map them back to `P` +export type PreservedDeepRequired = NonNullable extends Preserve + ? P + : { + [K in keyof T]-?: NonNullable extends Builtin + ? T[K] + : NonNullable extends AnyArray + ? T[K] + : IsObject> extends true + ? PreservedDeepRequired + : T[K] + } diff --git a/src/widgets/base.ts b/src/widgets/base.ts index ff48157..fb576bb 100644 --- a/src/widgets/base.ts +++ b/src/widgets/base.ts @@ -1,43 +1,50 @@ -import { DOMContent } from '@/types' +import { DOMContent, ResolvePreserve } from '@/types' import { Client } from '@alma/client' import { setDOMContent } from '@/utils' import WidgetsController from '../widgets_controller' -import { RenderingFunc, WidgetFactoryFunc } from './types' - -export interface WidgetConstructor { - new (almaClient: Client, options: WidgetSettings): T -} - -export interface WidgetSettings { - // CSS selector to a single DOM element, or a DOM Element itself into which the widget must render - container: string | HTMLElement - // Override the rendering of a widget by providing your own rendering function. - render?: RenderingFunc -} - -type WidgetConfig = WidgetSettings - -export abstract class Widget { - protected _config: WidgetConfig - protected readonly _almaClient: Client - - protected constructor(almaClient: Client, options: WidgetSettings) { - this._config = options - this._almaClient = almaClient +import { WidgetFactoryFunc } from './types' +import { + DefaultWidgetConfig, + makeConfig, + WidgetConfig, + BaseWidgetSettings, + BaseTemplateSettings, + BaseClassesSettings, +} from '@/widgets/config' + +export type WidgetSettings = BaseWidgetSettings + +export type ConstructorFor = T extends Widget + ? new (almaClient: Client, settings: ResolvePreserve) => T + : never + +export type SettingsFor = T extends Widget + ? ResolvePreserve + : never + +export abstract class Widget { + private readonly _config: WidgetConfig + + constructor(protected readonly almaClient: Client, settings: ResolvePreserve) { + this._config = makeConfig(this.defaultConfig(), settings) } - get config(): WidgetConfig { + abstract defaultConfig(): DefaultWidgetConfig + + get config(): WidgetConfig { return { ...this._config } } private get container(): HTMLElement { - let container: HTMLElement | null + let container: HTMLElement if (typeof this._config.container === 'string') { - container = document.querySelector(this._config.container) + const foundElement = document.querySelector(this._config.container) - if (!container) { + if (!foundElement) { throw new Error(`Container element '${this._config.container}' not found`) + } else { + container = foundElement as HTMLElement } } else { container = this._config.container @@ -46,9 +53,19 @@ export abstract class Widget { return container } + protected abstract prepare(almaClient: Client): Promise + protected abstract render( + renderingContext: unknown, + createWidget: WidgetFactoryFunc + ): Promise + + mount(dom: DOMContent): void { + setDOMContent(this.container, dom) + } + async refresh(): Promise { - const renderingContext = await this.prepare(this._almaClient) - const nestedWidgets = new WidgetsController(this._almaClient) + const renderingContext = await this.prepare(this.almaClient) + const nestedWidgets = new WidgetsController(this.almaClient) let dom: DOMContent const createWidget = nestedWidgets.create.bind(nestedWidgets) @@ -63,15 +80,4 @@ export abstract class Widget { // Render any nested widget that might have been added by the rendering of the widget await nestedWidgets.render() } - - mount(dom: DOMContent): void { - setDOMContent(this.container, dom) - } - - protected abstract prepare(almaClient: Client): Promise - - protected abstract render( - renderingContext: unknown, - createWidget: WidgetFactoryFunc - ): Promise } diff --git a/src/widgets/config.ts b/src/widgets/config.ts new file mode 100644 index 0000000..3fc6d51 --- /dev/null +++ b/src/widgets/config.ts @@ -0,0 +1,82 @@ +import isPlainObject from 'lodash.isplainobject' +import { MarkOptional } from 'ts-essentials' + +import { DOMContent, IObject, Preserve, PreservedDeepRequired, ResolvePreserve } from '@/types' +import { RenderingFunc } from '@/widgets/types' + +// *Settings: interfaces for lib consumers +///////////////////////////////////////////// +export interface BaseTemplateSettings { + [K: string]: ((...args: never[]) => DOMContent) | undefined +} + +export interface BaseClassesSettings { + root?: string +} + +// Widget Settings are composed of top-level settings + optional template settings & classes settings +export interface BaseWidgetSettings< + Tpl extends BaseTemplateSettings, + Cls extends BaseClassesSettings +> { + container: Preserve + render?: RenderingFunc + + templates?: Tpl + classes?: Cls +} + +/* *Config: "complete" representations of settings for internal use */ + +// A Widget config is derived from its Settings type, making all keys +// required except `render` () +export type WidgetConfig = T extends BaseWidgetSettings + ? MarkOptional, 'render'> + : never + +// A "default widget config" represents the shape of the default values object used to initialize a +// config. It's the Config type itself, with 'container' marked optional as it does not make a lot of +// sense to have a default value for this property. +export type DefaultWidgetConfig = T extends BaseWidgetSettings + ? MarkOptional, 'render' | 'container'> + : never + +/** + * Merges a default config object with a settings object, to build a widget's internal config object + * + * @param defaults The default values to use as a base config + * @param settings The settings that should override the default values + * + * @return WidgetConfig the fully merged config object + */ +export function makeConfig( + defaults: DefaultWidgetConfig, + settings: ResolvePreserve +): WidgetConfig { + const result: WidgetConfig = {} as WidgetConfig + const sources = [defaults, settings] + + function mergeObjects(base: IObject, source: IObject): IObject { + for (const key of Object.keys(source)) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + if (isPlainObject(source[key])) { + if (!base[key] || !isPlainObject(base[key])) { + base[key] = {} + } + mergeObjects(base[key] as IObject, source[key] as IObject) + } else { + base[key] = source[key] + } + } + } + + return base + } + + for (let idx = 0; idx < 2; idx++) { + const src = sources[idx] + mergeObjects(result, src as IObject) + } + + return result +} diff --git a/src/widgets/how_it_works/default_templates.ts b/src/widgets/how_it_works/default_templates.ts index 22eb594..d460eb8 100644 --- a/src/widgets/how_it_works/default_templates.ts +++ b/src/widgets/how_it_works/default_templates.ts @@ -1,5 +1,5 @@ -import { HowItWorksTemplates, HowItWorksWidgetClasses } from './types' -import { DOMContent } from '../../types' +import { HowItWorksTemplatesConfig, HowItWorksClassesConfig } from './types' +import { DOMContent } from '@/types' import { createListElement, createRootElement, @@ -23,7 +23,7 @@ function logo(): DOMContent { return imageWithSrc(almaLogo) } -function cta(openModal: EventHandlerNonNull, classes: HowItWorksWidgetClasses): DOMContent { +function cta(openModal: EventHandlerNonNull, classes: HowItWorksClassesConfig): DOMContent { const cta = createRootElement(classes.cta) cta.appendChild(document.createTextNode('Comment ça marche ?')) cta.appendChild(imageWithSrc(infoLogo)) @@ -34,7 +34,7 @@ function cta(openModal: EventHandlerNonNull, classes: HowItWorksWidgetClasses): function modal( content: DOMContent, closeModal: EventHandlerNonNull, - classes: HowItWorksWidgetClasses + classes: HowItWorksClassesConfig ): DOMContent { const wrapper = createRootElement(classes.modal.wrapper) @@ -63,7 +63,7 @@ function modal( function modalContent( paymentPlans: IPaymentPlan[], closeModal: EventHandlerNonNull, - classes: HowItWorksWidgetClasses + classes: HowItWorksClassesConfig ): DOMContent { const contentRoot = createRootElement(classes.modal.content.wrapper) @@ -149,6 +149,8 @@ function modalContent( // Make first sample payment plan selected & visible setDOMContent(planDetailsWrapper, planDetails[0]) + + // TODO: Remove non-null assertion / handle null case paymentPlansButtonsList .querySelector(`.${classes.modal.content.paymentPlanButton.button}`)! .classList.add(classes.modal.content.paymentPlanButton.selected) @@ -178,11 +180,9 @@ function modalContent( return contentRoot } -export const templates: HowItWorksTemplates = { +export const defaultTemplates: HowItWorksTemplatesConfig = { logo, cta, modal, modalContent, } - -export default templates diff --git a/src/widgets/how_it_works/index.ts b/src/widgets/how_it_works/index.ts index 2467e16..021e942 100644 --- a/src/widgets/how_it_works/index.ts +++ b/src/widgets/how_it_works/index.ts @@ -1,88 +1,78 @@ import './styles.scss' import { Widget } from '../base' -import { DOMContent } from '@/types' +import { DOMContent, ResolvePreserve } from '@/types' import { Client } from '@alma/client' import { setDOMContent, createRootElement } from '../../utils' -import defaultTemplates from './default_templates' -import { HowItWorksConfig, HowItWorksSettings, HowItWorksWidgetClasses } from './types' +import { defaultTemplates } from './default_templates' +import { HowItWorksSettings } from './types' import { IEligibility, IPaymentPlan } from '@alma/client/dist/types/entities/eligibility' +import { DefaultWidgetConfig } from '@/widgets/config' -const defaultClasses: HowItWorksWidgetClasses = { - root: 'alma-how_it_works', - logo: 'alma-how_it_works--logo', - cta: 'alma-how_it_works--cta', - modal: { - root: 'alma-modal', - wrapper: 'alma-modal--wrapper', - frame: 'alma-modal--frame', - closeButton: 'alma-modal--close-btn', - cardLogos: { - wrapper: 'alma-hiw_content--card-logos', - logo: 'alma-hiw_content--card-logo', - }, - content: { - wrapper: 'alma-hiw_content--wrapper', - logoContainer: 'alma-hiw_content--logo', - paymentPlansWrapper: 'alma-hiw_content--plans', - paymentPlansButtons: 'alma-hiw_content--plans-btns', - paymentPlanButton: { - button: 'alma-hiw_content--plan-btn', - selected: 'alma-hiw_content--plan-btn__selected', - installmentsCount: 'alma-hiw_content--plan-btn--installments_count', - }, - paymentPlanDetailsWrapper: 'alma-hiw_content--plan-details', - creditCardPayment: 'alma-hiw_content--cc-payment', - installmentAmount: 'alma-hiw_content--installment-amount', - installmentFees: 'alma-hiw_content--installment-fees', - installmentDate: 'alma-hiw_content--installment-date', - footer: { - wrapper: 'alma-hiw_content--footer', - closeButton: 'alma-hiw_content--close-btn', - }, - }, - }, -} +export class HowItWorksWidget extends Widget { + private readonly modalWrapper: HTMLElement + + constructor(almaClient: Client, settings: ResolvePreserve) { + super(almaClient, settings) -export class HowItWorksWidget extends Widget { - private modalWrapper: HTMLElement | null + this.modalWrapper = createRootElement(this.config.classes.modal.root) + this.modalWrapper.style.display = 'none' + } - constructor(almaClient: Client, options: HowItWorksSettings) { - // Inject default templates & classes into the given options - options = { + defaultConfig(): DefaultWidgetConfig { + return { displayLogo: true, displayInfoIcon: true, - ctaContent: 'Comment ça marche ?', + ctaContent: '', samplePlans: [], - ...options, - templates: { - ...defaultTemplates, - ...options.templates, - }, classes: { - ...defaultClasses, - ...options.classes, + root: 'alma-how_it_works', + logo: 'alma-how_it_works--logo', + cta: 'alma-how_it_works--cta', + modal: { + root: 'alma-modal', + wrapper: 'alma-modal--wrapper', + frame: 'alma-modal--frame', + closeButton: 'alma-modal--close-btn', + cardLogos: { + wrapper: 'alma-hiw_content--card-logos', + logo: 'alma-hiw_content--card-logo', + }, + content: { + wrapper: 'alma-hiw_content--wrapper', + logoContainer: 'alma-hiw_content--logo', + paymentPlansWrapper: 'alma-hiw_content--plans', + paymentPlansButtons: 'alma-hiw_content--plans-btns', + paymentPlanButton: { + button: 'alma-hiw_content--plan-btn', + selected: 'alma-hiw_content--plan-btn__selected', + installmentsCount: 'alma-hiw_content--plan-btn--installments_count', + }, + paymentPlanDetailsWrapper: 'alma-hiw_content--plan-details', + creditCardPayment: 'alma-hiw_content--cc-payment', + installmentAmount: 'alma-hiw_content--installment-amount', + installmentFees: 'alma-hiw_content--installment-fees', + installmentDate: 'alma-hiw_content--installment-date', + footer: { + wrapper: 'alma-hiw_content--footer', + closeButton: 'alma-hiw_content--close-btn', + }, + }, + }, }, + templates: defaultTemplates, } - - super(almaClient, options) - - this.modalWrapper = null - } - - get config(): HowItWorksConfig { - return { ...this._config } as HowItWorksConfig } - public openModal(e: Event) { + openModal(e: Event): boolean { e.preventDefault() - this.modalWrapper!.style.display = 'block' + this.modalWrapper.style.display = 'block' return false } - public closeModal(e: Event) { + closeModal(e: Event): boolean { e.preventDefault() - this.modalWrapper!.style.display = 'none' + this.modalWrapper.style.display = 'none' return false } @@ -95,6 +85,9 @@ export class HowItWorksWidget extends Widget { }, })) as IEligibility[] + // TODO: Remove non-null assertion. Requires using type EligibleEligibility[] above, but can + // we actually guarantee that 300€ in 3 or 4 installments will always be eligible for any + // given merchant? It might be safer to just hardcode some samples! return samplePlans.filter((p) => p.eligible).map((p) => p.payment_plan!) } else { return this.config.samplePlans @@ -125,8 +118,6 @@ export class HowItWorksWidget extends Widget { this.config.classes ) - this.modalWrapper = createRootElement(this.config.classes.modal.root) - this.modalWrapper.style.display = 'none' setDOMContent( this.modalWrapper, this.config.templates.modal(content, closeModal, this.config.classes) diff --git a/src/widgets/how_it_works/types.ts b/src/widgets/how_it_works/types.ts index 7cfc0c8..e5d1a32 100644 --- a/src/widgets/how_it_works/types.ts +++ b/src/widgets/how_it_works/types.ts @@ -1,9 +1,9 @@ -import { DeepRequired, DOMContent } from '../../types' -import { WidgetSettings } from '../base' +import { DOMContent } from '@/types' +import { BaseClassesSettings, BaseTemplateSettings, BaseWidgetSettings } from '../config' import { IPaymentPlan } from '@alma/client/dist/types/entities/eligibility' +import { DeepRequired } from 'ts-essentials' -export type HowItWorksWidgetClassesOption = { - root?: string +interface HIWClasses extends BaseClassesSettings { logo?: string cta?: string modal?: { @@ -38,33 +38,28 @@ export type HowItWorksWidgetClassesOption = { } } -export type HowItWorksWidgetClasses = DeepRequired +export type HowItWorksClassesConfig = DeepRequired -export type HowItWorksTemplatesOption = { - logo?: (classes: HowItWorksWidgetClasses) => DOMContent - cta?: (openModal: EventHandlerNonNull, classes: HowItWorksWidgetClasses) => DOMContent +interface HIWTemplates extends BaseTemplateSettings { + logo?: (classes: HowItWorksClassesConfig) => DOMContent + cta?: (openModal: EventHandlerNonNull, classes: HowItWorksClassesConfig) => DOMContent modal?: ( content: DOMContent, closeModal: EventHandlerNonNull, - classes: HowItWorksWidgetClasses + classes: HowItWorksClassesConfig ) => DOMContent modalContent?: ( paymentPlans: IPaymentPlan[], closeModal: EventHandlerNonNull, - classes: HowItWorksWidgetClasses + classes: HowItWorksClassesConfig ) => DOMContent } -export type HowItWorksTemplates = DeepRequired +export type HowItWorksTemplatesConfig = DeepRequired -type HowItWorksOptions = { - templates?: HowItWorksTemplatesOption - classes?: HowItWorksWidgetClassesOption +export interface HowItWorksSettings extends BaseWidgetSettings { displayLogo?: boolean displayInfoIcon?: boolean ctaContent?: DOMContent samplePlans?: IPaymentPlan[] } - -export type HowItWorksSettings = HowItWorksOptions & WidgetSettings -export type HowItWorksConfig = DeepRequired & WidgetSettings diff --git a/src/widgets/payment_plan/default_templates.ts b/src/widgets/payment_plan/default_templates.ts index 1bc0b2e..0966886 100644 --- a/src/widgets/payment_plan/default_templates.ts +++ b/src/widgets/payment_plan/default_templates.ts @@ -1,14 +1,15 @@ -import { DOMContent, integer } from '../../types' +import { DOMContent, integer, ResolvePreserve } from '@/types' import { joinInstallmentsCounts, createRootElement, formatCents, imageWithSrc } from '../../utils' -import { EligibleEligibility } from '@alma/client/dist/types/entities/eligibility' -import { PaymentPlanConfig, PaymentPlanTemplatesOption, PaymentPlanWidgetClasses } from './types' +import { EligibleEligibility, IInstallment } from '@alma/client/dist/types/entities/eligibility' +import { PaymentPlanClassesConfig, PaymentPlanSettings } from './types' import { WidgetFactoryFunc } from '../types' import HowItWorksWidget from '../how_it_works' -import { HowItWorksSettings, HowItWorksWidgetClasses } from '../how_it_works/types' +import { HowItWorksSettings, HowItWorksClassesConfig } from '../how_it_works/types' import infoLogo from '../../assets/info.svg' +import { WidgetConfig } from '@/widgets/config' -function howItWorksCtaTemplate(title: HTMLElement, classes: PaymentPlanWidgetClasses) { - return (openModal: EventHandlerNonNull, nestedClasses: HowItWorksWidgetClasses): DOMContent => { +function howItWorksCtaTemplate(title: HTMLElement, classes: PaymentPlanClassesConfig) { + return (openModal: EventHandlerNonNull, nestedClasses: HowItWorksClassesConfig): DOMContent => { const cta = createRootElement(nestedClasses.cta) cta.appendChild(title) @@ -23,7 +24,7 @@ function howItWorksCtaTemplate(title: HTMLElement, classes: PaymentPlanWidgetCla function titleTemplate( eligiblePlans: EligibleEligibility[], - config: PaymentPlanConfig, + config: WidgetConfig, createWidget: WidgetFactoryFunc ): HTMLElement { const titleWrapper = createRootElement(config.classes.title) @@ -50,7 +51,7 @@ function titleTemplate( return titleWrapper } -function _installmentTemplate(content: string, classes: PaymentPlanWidgetClasses): HTMLElement { +function _installmentTemplate(content: string, classes: PaymentPlanClassesConfig): HTMLElement { const installment = document.createElement('span') installment.className = classes.paymentPlan.installmentAmount installment.innerHTML = content @@ -60,8 +61,8 @@ function _installmentTemplate(content: string, classes: PaymentPlanWidgetClasses function paymentPlanTemplate( eligibility: EligibleEligibility, - config: PaymentPlanConfig, - createWidget: WidgetFactoryFunc + config: WidgetConfig, + _: WidgetFactoryFunc ): HTMLElement[] { const installmentsCountLabel = document.createElement('span') installmentsCountLabel.className = config.classes.paymentPlan.installmentsCount @@ -72,7 +73,7 @@ function paymentPlanTemplate( const installmentsData = [...eligibility.payment_plan] const equalInstallments = eligibility.payment_plan.every( - (p: any, idx: number, arr: Array) => { + (p: IInstallment, idx: number, arr: IInstallment[]) => { return p.purchase_amount + p.customer_fee === arr[0].purchase_amount + arr[0].customer_fee } ) @@ -99,7 +100,7 @@ function notEligibleTemplate( min: number, max: number, installmentsCounts: integer[], - config: PaymentPlanConfig, + config: WidgetConfig, createWidget: WidgetFactoryFunc ): HTMLElement { const titleWrapper = createRootElement(config.classes.title) @@ -115,15 +116,16 @@ function notEligibleTemplate( templates: { cta: howItWorksCtaTemplate(title, config.classes), }, - } as HowItWorksSettings) + // TODO: why does WidgetFactoryFunc "wrongly" resolves SettingsFor to WidgetSettings when + // widget is declared as `class Widget`, but correctly to + // HowItWorksSettings when it is declared as `class Widget`? + } as ResolvePreserve) return titleWrapper } -const templates: PaymentPlanTemplatesOption = { +export const defaultTemplates = { title: titleTemplate, paymentPlan: paymentPlanTemplate, notEligible: notEligibleTemplate, } - -export default templates diff --git a/src/widgets/payment_plan/index.ts b/src/widgets/payment_plan/index.ts index 95f3c4a..8e8bf06 100644 --- a/src/widgets/payment_plan/index.ts +++ b/src/widgets/payment_plan/index.ts @@ -6,45 +6,35 @@ import { Widget } from '../base' import { DOMContent, integer } from '@/types' import { Client } from '@alma/client' import { setDOMContent } from '@/utils' -import defaultTemplates from './default_templates' -import { PaymentPlanConfig, PaymentPlanSettings, PaymentPlanWidgetClasses } from './types' +import { defaultTemplates } from './default_templates' +import { PaymentPlanSettings } from './types' import { WidgetFactoryFunc } from '../types' - -const defaultClasses: PaymentPlanWidgetClasses = { - root: 'alma-payment_plan', - title: 'alma-payment_plan--title', - infoButton: 'alma-payment_plan--info_btn', - paymentPlan: { - root: 'alma-payment_plan--plan', - installmentsCount: 'alma-payment_plan--installments_count', - installmentsWrapper: 'alma-payment_plan--installments', - installmentAmount: 'alma-payment_plan--installment', - }, - notEligible: 'alma-payment_plan--not_eligible', -} - -export class PaymentPlanWidget extends Widget { - constructor(almaClient: Client, options: PaymentPlanSettings) { - // Inject default templates into the given options - options = { - ...options, - templates: { - ...defaultTemplates, - ...options.templates, - }, +import { DefaultWidgetConfig } from '@/widgets/config' + +type PaymentPlanDefaultConfig = DefaultWidgetConfig + +export class PaymentPlanWidget extends Widget { + defaultConfig(): PaymentPlanDefaultConfig { + return { + purchaseAmount: 100, + installmentsCount: 3, + minPurchaseAmount: null, + maxPurchaseAmount: null, + templates: defaultTemplates, classes: { - ...defaultClasses, - ...options.classes, + root: 'alma-payment_plan', + title: 'alma-payment_plan--title', + infoButton: 'alma-payment_plan--info_btn', + paymentPlan: { + root: 'alma-payment_plan--plan', + installmentsCount: 'alma-payment_plan--installments_count', + installmentsWrapper: 'alma-payment_plan--installments', + installmentAmount: 'alma-payment_plan--installment', + }, + notEligible: 'alma-payment_plan--not_eligible', }, } - - super(almaClient, options) - } - - get config(): PaymentPlanConfig { - return { ...this._config } as PaymentPlanConfig } - get installmentsCounts(): integer[] { let installmentsCounts = this.config.installmentsCount @@ -56,11 +46,11 @@ export class PaymentPlanWidget extends Widget { } protected async prepare(almaClient: Client): Promise { - const options = this._config as PaymentPlanConfig + const { purchaseAmount, minPurchaseAmount, maxPurchaseAmount } = this.config if ( - options.purchaseAmount < options.minPurchaseAmount || - options.purchaseAmount > options.maxPurchaseAmount + (minPurchaseAmount && purchaseAmount < minPurchaseAmount) || + (maxPurchaseAmount && purchaseAmount > maxPurchaseAmount) ) { return [ { @@ -70,8 +60,8 @@ export class PaymentPlanWidget extends Widget { }, constraints: { purchase_amount: { - minimum: options.minPurchaseAmount, - maximum: options.maxPurchaseAmount, + minimum: minPurchaseAmount, + maximum: maxPurchaseAmount, }, }, }, @@ -80,7 +70,7 @@ export class PaymentPlanWidget extends Widget { return almaClient.payments.eligibility({ payment: { - purchase_amount: options.purchaseAmount, + purchase_amount: purchaseAmount, installments_count: this.installmentsCounts, }, }) @@ -158,5 +148,3 @@ export class PaymentPlanWidget extends Widget { return root } } - -export default PaymentPlanWidget diff --git a/src/widgets/payment_plan/types.ts b/src/widgets/payment_plan/types.ts index 115be39..f7e1178 100644 --- a/src/widgets/payment_plan/types.ts +++ b/src/widgets/payment_plan/types.ts @@ -1,51 +1,47 @@ -import { DeepRequired, DOMContent, integer } from '../../types' -import { WidgetSettings } from '../base' +import { DeepRequired, DOMContent, integer } from '@/types' +import { BaseWidgetSettings, WidgetConfig } from '../config' import { EligibleEligibility } from '@alma/client/dist/types/entities/eligibility' import { WidgetFactoryFunc } from '../types' +import { BaseClassesSettings, BaseTemplateSettings } from '@/widgets/config' -export type PaymentPlanWidgetClassesOption = { - root?: string - title?: string - infoButton?: string - paymentPlan?: { - root?: string - installmentsCount?: string - installmentsWrapper?: string - installmentAmount?: string - } - notEligible?: string -} - -export type PaymentPlanWidgetClasses = DeepRequired - -export type PaymentPlanTemplatesOption = { +interface PaymentPlanTemplates extends BaseTemplateSettings { title?: ( eligiblePlans: EligibleEligibility[], - config: PaymentPlanConfig, + config: WidgetConfig, createWidget: WidgetFactoryFunc ) => DOMContent paymentPlan?: ( eligibility: EligibleEligibility, - config: PaymentPlanConfig, + config: WidgetConfig, createWidget: WidgetFactoryFunc ) => DOMContent notEligible?: ( min: number, max: number, installmentsCounts: integer[], - config: PaymentPlanConfig, + config: WidgetConfig, createWidget: WidgetFactoryFunc ) => DOMContent } -type PaymentPlanOptions = { +interface PaymentPlanClasses extends BaseClassesSettings { + title?: string + infoButton?: string + paymentPlan?: { + root?: string + installmentsCount?: string + installmentsWrapper?: string + installmentAmount?: string + } + notEligible?: string +} + +export type PaymentPlanClassesConfig = DeepRequired + +export interface PaymentPlanSettings + extends BaseWidgetSettings { purchaseAmount: integer installmentsCount: integer | integer[] - minPurchaseAmount?: integer - maxPurchaseAmount?: integer - templates?: PaymentPlanTemplatesOption - classes?: PaymentPlanWidgetClassesOption + minPurchaseAmount?: integer | null + maxPurchaseAmount?: integer | null } - -export type PaymentPlanSettings = PaymentPlanOptions & WidgetSettings -export type PaymentPlanConfig = DeepRequired & WidgetSettings diff --git a/src/widgets/types.ts b/src/widgets/types.ts index 90b452f..d4f2b43 100644 --- a/src/widgets/types.ts +++ b/src/widgets/types.ts @@ -1,8 +1,8 @@ -import { Widget, WidgetConstructor, WidgetSettings } from './base' +import { ConstructorFor, SettingsFor } from './base' import { DOMContent } from '@/types' export interface WidgetFactoryFunc { - (widgetCtor: WidgetConstructor, options: WidgetSettings): Widget + (widgetCtor: ConstructorFor, settings: SettingsFor): T } export interface RenderingFunc { diff --git a/src/widgets_controller.ts b/src/widgets_controller.ts index 152c8e0..661fde5 100644 --- a/src/widgets_controller.ts +++ b/src/widgets_controller.ts @@ -1,16 +1,13 @@ import { Client } from '@alma/client' -import { Widget, WidgetConstructor, WidgetSettings } from './widgets/base' +import { Widget, WidgetSettings, ConstructorFor, SettingsFor } from './widgets/base' export class WidgetsController { - private readonly almaClient: Client - private widgets: Widget[] = [] + private widgets: Widget[] = [] - constructor(almaClient: Client) { - this.almaClient = almaClient - } + constructor(private readonly almaClient: Client) {} - create(widgetCtor: WidgetConstructor, options: WidgetSettings): Widget { - const widget = new widgetCtor(this.almaClient, options) + create(widgetCtor: ConstructorFor, settings: SettingsFor): T { + const widget = new widgetCtor(this.almaClient, settings) this.widgets.push(widget) return widget } From adba4c771ba2dd63403d08a62686a9380dd3e940 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 13:33:32 +0100 Subject: [PATCH 04/14] chore: update dependencies --- package-lock.json | 247 +++++++++++++++++++++++++++++++++++----------- package.json | 16 +-- 2 files changed, 197 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b5f8c6..a1a699a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2530,9 +2530,9 @@ "dev": true }, "@types/node": { - "version": "14.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.9.tgz", - "integrity": "sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw==", + "version": "14.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", + "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==", "dev": true }, "@types/normalize-package-data": { @@ -2596,18 +2596,76 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.1.tgz", - "integrity": "sha512-d7LeQ7dbUrIv5YVFNzGgaW3IQKMmnmKFneRWagRlGYOSfLJVaRbj/FrBNOBC1a3tVO+TgNq1GbHvRtg1kwL0FQ==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.2.tgz", + "integrity": "sha512-gQ06QLV5l1DtvYtqOyFLXD9PdcILYqlrJj2l+CGDlPtmgLUzc1GpqciJFIRvyfvgLALpnxYINFuw+n9AZhPBKQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.8.1", - "@typescript-eslint/scope-manager": "4.8.1", + "@typescript-eslint/experimental-utils": "4.8.2", + "@typescript-eslint/scope-manager": "4.8.2", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.2.tgz", + "integrity": "sha512-hpTw6o6IhBZEsQsjuw/4RWmceRyESfAiEzAEnXHKG1X7S5DXFaZ4IO1JO7CW1aQ604leQBzjZmuMI9QBCAJX8Q==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.8.2", + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/typescript-estree": "4.8.2", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.8.2.tgz", + "integrity": "sha512-qHQ8ODi7mMin4Sq2eh/6eu03uVzsf5TX+J43xRmiq8ujng7ViQSHNPLOHGw/Wr5dFEoxq/ubKhzClIIdQy5q3g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/visitor-keys": "4.8.2" + } + }, + "@typescript-eslint/types": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.8.2.tgz", + "integrity": "sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.2.tgz", + "integrity": "sha512-HToGNwI6fekH0dOw3XEVESUm71Onfam0AKin6f26S2FtUmO7o3cLlWgrIaT1q3vjB3wCTdww3Dx2iGq5wtUOCg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/visitor-keys": "4.8.2", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.2.tgz", + "integrity": "sha512-Vg+/SJTMZJEKKGHW7YC21QxgKJrSbxoYYd3MEUGtW7zuytHuEcksewq0DUmo4eh/CTNrVJGSdIY9AtRb6riWFw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "@typescript-eslint/experimental-utils": { @@ -2625,15 +2683,59 @@ } }, "@typescript-eslint/parser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.8.1.tgz", - "integrity": "sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.8.2.tgz", + "integrity": "sha512-u0leyJqmclYr3KcXOqd2fmx6SDGBO0MUNHHAjr0JS4Crbb3C3d8dwAdlazy133PLCcPn+aOUFiHn72wcuc5wYw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.8.1", - "@typescript-eslint/types": "4.8.1", - "@typescript-eslint/typescript-estree": "4.8.1", + "@typescript-eslint/scope-manager": "4.8.2", + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/typescript-estree": "4.8.2", "debug": "^4.1.1" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.8.2.tgz", + "integrity": "sha512-qHQ8ODi7mMin4Sq2eh/6eu03uVzsf5TX+J43xRmiq8ujng7ViQSHNPLOHGw/Wr5dFEoxq/ubKhzClIIdQy5q3g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/visitor-keys": "4.8.2" + } + }, + "@typescript-eslint/types": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.8.2.tgz", + "integrity": "sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.2.tgz", + "integrity": "sha512-HToGNwI6fekH0dOw3XEVESUm71Onfam0AKin6f26S2FtUmO7o3cLlWgrIaT1q3vjB3wCTdww3Dx2iGq5wtUOCg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/visitor-keys": "4.8.2", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.2.tgz", + "integrity": "sha512-Vg+/SJTMZJEKKGHW7YC21QxgKJrSbxoYYd3MEUGtW7zuytHuEcksewq0DUmo4eh/CTNrVJGSdIY9AtRb6riWFw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "@typescript-eslint/scope-manager": { @@ -3362,9 +3464,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001159", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001159.tgz", - "integrity": "sha512-w9Ph56jOsS8RL20K9cLND3u/+5WASWdhC/PPrf+V3/HsM3uHOavWOR1Xzakbv4Puo/srmPHudkmCRWM7Aq+/UA==", + "version": "1.0.30001161", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001161.tgz", + "integrity": "sha512-JharrCDxOqPLBULF9/SPa6yMcBRTjZARJ6sc3cuKrPfyIk64JN6kuMINWqA99Xc8uElMFcROliwtz0n9pYej+g==", "dev": true }, "capture-exit": { @@ -3917,18 +4019,18 @@ "dev": true }, "core-js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", - "integrity": "sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.0.tgz", + "integrity": "sha512-W2VYNB0nwQQE7tKS7HzXd7r2y/y2SVJl4ga6oH/dnaLFzM0o2lB2P3zCkWj5Wc/zyMYjtgd5Hmhk0ObkQFZOIA==", "dev": true }, "core-js-compat": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.7.0.tgz", - "integrity": "sha512-V8yBI3+ZLDVomoWICO6kq/CD28Y4r1M7CWeO4AGpMdMfseu8bkSubBmUPySMGKRTS+su4XQ07zUkAsiu9FCWTg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.0.tgz", + "integrity": "sha512-o9QKelQSxQMYWHXc/Gc4L8bx/4F7TTraE5rhuN8I7mKBt5dBIUpXpIR3omv70ebr8ST5R3PqbDQr+ZI3+Tt1FQ==", "dev": true, "requires": { - "browserslist": "^4.14.6", + "browserslist": "^4.14.7", "semver": "7.0.0" }, "dependencies": { @@ -4252,18 +4354,18 @@ "dev": true }, "csso": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.1.1.tgz", - "integrity": "sha512-Rvq+e1e0TFB8E8X+8MQjHSY6vtol45s5gxtLI/018UsAn2IBMmwNEZRM/h+HVnAJRHjasLIKKUO3uvoMM28LvA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", "dev": true, "requires": { - "css-tree": "^1.0.0" + "css-tree": "^1.1.2" }, "dependencies": { "css-tree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.1.tgz", - "integrity": "sha512-NVN42M2fjszcUNpDbdkvutgQSlFYsr1z7kqeuCagHnNLBfYor6uP1WL1KrkmdYZ5Y1vTBCIOI/C/+8T98fJ71w==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", + "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", "dev": true, "requires": { "mdn-data": "2.0.14", @@ -4692,9 +4794,9 @@ } }, "electron-to-chromium": { - "version": "1.3.603", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.603.tgz", - "integrity": "sha512-J8OHxOeJkoSLgBXfV9BHgKccgfLMHh+CoeRo6wJsi6m0k3otaxS/5vrHpMNSEYY4MISwewqanPOuhAtuE8riQQ==", + "version": "1.3.610", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.610.tgz", + "integrity": "sha512-eFDC+yVQpEhtlapk4CYDPfV9ajF9cEof5TBcO49L1ETO+aYogrKWDmYpZyxBScMNe8Bo/gJamH4amQ4yyvXg4g==", "dev": true }, "emittery": { @@ -4759,9 +4861,9 @@ } }, "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", @@ -4769,6 +4871,7 @@ "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", "is-regex": "^1.1.1", "object-inspect": "^1.8.0", "object-keys": "^1.1.1", @@ -6618,6 +6721,12 @@ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8192,9 +8301,9 @@ "dev": true }, "lint-staged": { - "version": "10.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.1.tgz", - "integrity": "sha512-fTkTGFtwFIJJzn/PbUO3RXyEBHIhbfYBE7+rJyLcOXabViaO/h6OslgeK6zpeUtzkDrzkgyAYDTLAwx6JzDTHw==", + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.2.tgz", + "integrity": "sha512-e8AYR1TDlzwB8VVd38Xu2lXDZf6BcshVqKVuBQThDJRaJLobqKnpbm4dkwJ2puypQNbLr9KF/9mfA649mAGvjA==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -8215,9 +8324,9 @@ } }, "listr2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.2.tgz", - "integrity": "sha512-AajqcZEUikF2ioph6PfH3dIuxJclhr3i3kHgTOP0xeXdWQohrvJAAmqVcV43/GI987HFY/vzT73jYXoa4esDHg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.3.tgz", + "integrity": "sha512-vUb80S2dSUi8YxXahO8/I/s29GqnOL8ozgHVLjfWQXa03BNEeS1TpBLjh2ruaqq5ufx46BRGvfymdBSuoXET5w==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -12705,13 +12814,14 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", + "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "es-abstract": "^1.18.0-next.1" } }, "object.pick": { @@ -12724,14 +12834,14 @@ } }, "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" } }, @@ -14027,9 +14137,9 @@ "dev": true }, "prettier": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.0.tgz", - "integrity": "sha512-yYerpkvseM4iKD/BXLYUkQV5aKt4tQPqaGW6EsZjzyu0r7sVZZNPJW4Y8MyKmicp6t42XUPcBVA+H6sB3gqndw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", "dev": true }, "pretty-bytes": { @@ -15165,9 +15275,9 @@ } }, "semantic-release": { - "version": "17.2.4", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.2.4.tgz", - "integrity": "sha512-NcYFT+duzg+eE57s9lZYRRkxzyiXGvwFdPvS4U+8TYo+vb0UZpQuFi63hFgoLAOF4lo5yj2v79GsG9hDC66EQw==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.3.0.tgz", + "integrity": "sha512-enhDayMZRP4nWcWAMBFHHB7THRaIcRdUAZv3lxd65pXs2ttzay7IeCvRRrGayRWExtnY0ulwRz5Ycp88Dv/UeQ==", "dev": true, "requires": { "@semantic-release/commit-analyzer": "^8.0.0", @@ -16659,6 +16769,27 @@ "es-abstract": "^1.17.2", "has-symbols": "^1.0.1", "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "uuid": { diff --git a/package.json b/package.json index 4eecbf2..a607258 100644 --- a/package.json +++ b/package.json @@ -42,11 +42,11 @@ "@semantic-release/git": "^9.0.0", "@types/jest": "^26.0.14", "@types/lodash.isplainobject": "^4.0.6", - "@types/node": "^14.11.10", - "@typescript-eslint/eslint-plugin": "^4.4.1", - "@typescript-eslint/parser": "^4.4.1", + "@types/node": "^14.14.10", + "@typescript-eslint/eslint-plugin": "^4.8.2", + "@typescript-eslint/parser": "^4.8.2", "commitizen": "^4.2.1", - "core-js": "^3.6.5", + "core-js": "^3.8.0", "cz-conventional-changelog": "^3.3.0", "eslint": "^7.11.0", "eslint-config-prettier": "^6.15.0", @@ -54,12 +54,12 @@ "husky": "^4.3.0", "jest": "^26.6.0", "jest-config": "^26.6.0", - "lint-staged": "^10.4.2", - "microbundle": "olance/microbundle#rollup/config-override", - "prettier": "^2.1.2", + "lint-staged": "^10.5.2", + "microbundle": "github:olance/microbundle#rollup/config-override", + "prettier": "^2.2.1", "rollup": "^1.32.1", "sass": "^1.29.0", - "semantic-release": "^17.2.2", + "semantic-release": "^17.3.0", "ts-essentials": "^7.0.1", "ts-jest": "^26.4.1", "typedoc": "^0.19.2", From a7647b97f2fc2357b86546f756e69d7186c6e282 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 16:31:22 +0100 Subject: [PATCH 05/14] test(jest): improve/fix configuration of jest --- __mocks__/fileMock.js | 1 + __mocks__/styleMock.js | 1 + package-lock.json | 6 ++++++ package.json | 5 ++++- 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 __mocks__/fileMock.js create mode 100644 __mocks__/styleMock.js diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js new file mode 100644 index 0000000..0e56c5b --- /dev/null +++ b/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub' diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js new file mode 100644 index 0000000..4ba52ba --- /dev/null +++ b/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/package-lock.json b/package-lock.json index a1a699a..5ff9755 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5303,6 +5303,12 @@ } } }, + "expect-type": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-0.9.1.tgz", + "integrity": "sha512-DdpucVq/ktW+P0VIR5EVdqK4ixh0WDSlX/qYoS1EpwpyjyqZWcnEx34xCkCYVv3RaP+MHFY43cUawHPeHdmm8Q==", + "dev": true + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", diff --git a/package.json b/package.json index a607258..5ef3899 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "eslint": "^7.11.0", "eslint-config-prettier": "^6.15.0", "eslint-plugin-jest": "^24.1.0", + "expect-type": "^0.9.1", "husky": "^4.3.0", "jest": "^26.6.0", "jest-config": "^26.6.0", @@ -164,8 +165,10 @@ "transform": { ".(ts|tsx)$": "ts-jest" }, - "testEnvironment": "node", + "testEnvironment": "jsdom", "moduleNameMapper": { + "\\.s?css": "/__mocks__/styleMock.js", + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", "^@/(.*)$": [ "/src/$1" ], From 4d02cc005d58e5f5997ee9ff0668fa9b0b4e25bb Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 16:36:53 +0100 Subject: [PATCH 06/14] refactor(exports): remove internal default exports, as modules already export named exports --- src/index.ts | 2 +- src/widgets/base.ts | 2 +- src/widgets/how_it_works/index.ts | 2 -- src/widgets/payment_plan/default_templates.ts | 2 +- src/widgets_controller.ts | 2 -- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9fe3289..5aaf718 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import './utils/polyfills' import './scss/main.scss' import { ApiMode, Client } from '@alma/client' -import WidgetsController from './widgets_controller' +import { WidgetsController } from './widgets_controller' import * as widgets from './widgets' import * as utils from './utils' diff --git a/src/widgets/base.ts b/src/widgets/base.ts index fb576bb..cee3304 100644 --- a/src/widgets/base.ts +++ b/src/widgets/base.ts @@ -1,7 +1,7 @@ import { DOMContent, ResolvePreserve } from '@/types' import { Client } from '@alma/client' import { setDOMContent } from '@/utils' -import WidgetsController from '../widgets_controller' +import { WidgetsController } from '@/widgets_controller' import { WidgetFactoryFunc } from './types' import { DefaultWidgetConfig, diff --git a/src/widgets/how_it_works/index.ts b/src/widgets/how_it_works/index.ts index 021e942..efdbc6d 100644 --- a/src/widgets/how_it_works/index.ts +++ b/src/widgets/how_it_works/index.ts @@ -128,5 +128,3 @@ export class HowItWorksWidget extends Widget { return root } } - -export default HowItWorksWidget diff --git a/src/widgets/payment_plan/default_templates.ts b/src/widgets/payment_plan/default_templates.ts index 0966886..7e78e6a 100644 --- a/src/widgets/payment_plan/default_templates.ts +++ b/src/widgets/payment_plan/default_templates.ts @@ -3,7 +3,7 @@ import { joinInstallmentsCounts, createRootElement, formatCents, imageWithSrc } import { EligibleEligibility, IInstallment } from '@alma/client/dist/types/entities/eligibility' import { PaymentPlanClassesConfig, PaymentPlanSettings } from './types' import { WidgetFactoryFunc } from '../types' -import HowItWorksWidget from '../how_it_works' +import { HowItWorksWidget } from '../how_it_works' import { HowItWorksSettings, HowItWorksClassesConfig } from '../how_it_works/types' import infoLogo from '../../assets/info.svg' import { WidgetConfig } from '@/widgets/config' diff --git a/src/widgets_controller.ts b/src/widgets_controller.ts index 661fde5..40fe1ac 100644 --- a/src/widgets_controller.ts +++ b/src/widgets_controller.ts @@ -22,5 +22,3 @@ export class WidgetsController { await Promise.all(promises) } } - -export default WidgetsController From 1941276345775e55ab40a371363ab670125fc28c Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 16:41:54 +0100 Subject: [PATCH 07/14] test: add first dummy but "meaningful" tests --- test/alma-widgets.test.ts | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/test/alma-widgets.test.ts b/test/alma-widgets.test.ts index dbc9555..09782f8 100644 --- a/test/alma-widgets.test.ts +++ b/test/alma-widgets.test.ts @@ -1,8 +1,29 @@ -/** - * Dummy test - */ -describe('Dummy test', () => { - it('works if true is truthy', () => { - expect(true).toBeTruthy() +import { expectTypeOf } from 'expect-type' + +import { Widgets } from '../src' +import { WidgetsController } from '@/widgets_controller' +import { ApiMode } from '@alma/client' +import { PaymentPlanWidget } from '../src/widgets/payment_plan' +import { HowItWorksWidget } from '../src/widgets/how_it_works' + +describe('Widgets namespace', () => { + it('exports the initialize function', () => { + expect(Widgets.initialize).toBeDefined() + + expectTypeOf(Widgets.initialize).toEqualTypeOf< + (merchantId: string, apiMode: ApiMode) => WidgetsController + >() + }) + + it('exports the PaymentPlan widget', () => { + expect(Widgets.PaymentPlan).toBeDefined() + + expectTypeOf(Widgets.PaymentPlan).toEqualTypeOf() + }) + + it('exports the HowItWorks widget', () => { + expect(Widgets.HowItWorks).toBeDefined() + + expectTypeOf(Widgets.HowItWorks).toEqualTypeOf() }) }) From de68bbcfe3eab84a55554fc42abb8504e589615a Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 19:34:51 +0100 Subject: [PATCH 08/14] refactor: shuffle a few things around --- src/widgets/base.ts | 11 +---------- src/widgets/config.ts | 13 +++++++++---- src/widgets_controller.ts | 3 ++- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/widgets/base.ts b/src/widgets/base.ts index cee3304..6af3d36 100644 --- a/src/widgets/base.ts +++ b/src/widgets/base.ts @@ -3,16 +3,7 @@ import { Client } from '@alma/client' import { setDOMContent } from '@/utils' import { WidgetsController } from '@/widgets_controller' import { WidgetFactoryFunc } from './types' -import { - DefaultWidgetConfig, - makeConfig, - WidgetConfig, - BaseWidgetSettings, - BaseTemplateSettings, - BaseClassesSettings, -} from '@/widgets/config' - -export type WidgetSettings = BaseWidgetSettings +import { DefaultWidgetConfig, makeConfig, WidgetConfig, WidgetSettings } from '@/widgets/config' export type ConstructorFor = T extends Widget ? new (almaClient: Client, settings: ResolvePreserve) => T diff --git a/src/widgets/config.ts b/src/widgets/config.ts index 3fc6d51..d06ac40 100644 --- a/src/widgets/config.ts +++ b/src/widgets/config.ts @@ -4,8 +4,9 @@ import { MarkOptional } from 'ts-essentials' import { DOMContent, IObject, Preserve, PreservedDeepRequired, ResolvePreserve } from '@/types' import { RenderingFunc } from '@/widgets/types' -// *Settings: interfaces for lib consumers -///////////////////////////////////////////// +/* + * *Settings: interfaces for lib consumers + */ export interface BaseTemplateSettings { [K: string]: ((...args: never[]) => DOMContent) | undefined } @@ -26,7 +27,11 @@ export interface BaseWidgetSettings< classes?: Cls } -/* *Config: "complete" representations of settings for internal use */ +export type WidgetSettings = BaseWidgetSettings + +/* + * *Config: "complete" representations of settings for internal use + */ // A Widget config is derived from its Settings type, making all keys // required except `render` () @@ -49,7 +54,7 @@ export type DefaultWidgetConfig = T extends BaseWidgetSettings( +export function makeConfig( defaults: DefaultWidgetConfig, settings: ResolvePreserve ): WidgetConfig { diff --git a/src/widgets_controller.ts b/src/widgets_controller.ts index 40fe1ac..685453c 100644 --- a/src/widgets_controller.ts +++ b/src/widgets_controller.ts @@ -1,5 +1,6 @@ import { Client } from '@alma/client' -import { Widget, WidgetSettings, ConstructorFor, SettingsFor } from './widgets/base' +import { Widget, ConstructorFor, SettingsFor } from './widgets/base' +import { WidgetSettings } from '@/widgets/config' export class WidgetsController { private widgets: Widget[] = [] From a4d4cfa9d2a687b65907d0c627e31d2f0de0571b Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 19:35:29 +0100 Subject: [PATCH 09/14] chore(tests): additional improvement/fixes to testing setup --- package.json | 31 +++++++++++++++++++++---------- tsconfig.json | 1 + 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5ef3899..4721ef0 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,15 @@ "eslint:recommended", "prettier" ], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "varsIgnorePattern": "^_+$", + "argsIgnorePattern": "^_+$" + } + ] + }, "overrides": [ { "files": [ @@ -105,16 +114,7 @@ ], "plugins": [ "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "varsIgnorePattern": "^_+$", - "argsIgnorePattern": "^_+$" - } - ] - } + ] }, { "files": [ @@ -145,6 +145,17 @@ "parserOptions": { "ecmaVersion": 2019, "sourceType": "module" + }, + "rules": { + "jest/expect-expect": [ + "warn", + { + "assertFunctionNames": [ + "expect", + "expectTypeOf" + ] + } + ] } } ] diff --git a/tsconfig.json b/tsconfig.json index fd402ce..49a4033 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "strict": true, "sourceMap": true, "declaration": true, + "esModuleInterop": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, From 1b834cb945b8ec570538eb0b39e38e1485e761e1 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 19:36:14 +0100 Subject: [PATCH 10/14] test(config): test the makeConfig function --- test/config.test.ts | 117 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 test/config.test.ts diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 0000000..a65adb1 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,117 @@ +import { expectTypeOf } from 'expect-type' + +import { + BaseClassesSettings, + BaseTemplateSettings, + BaseWidgetSettings, + DefaultWidgetConfig, + makeConfig, + WidgetConfig, + WidgetSettings, +} from '@/widgets/config' +import { DOMContent, ResolvePreserve } from '../src/types' +import { RenderingFunc } from '../src/widgets/types' + +describe('makeConfig', () => { + it('has the correct signature', () => { + type expectedFuncSignature = ( + defaults: DefaultWidgetConfig, + settings: ResolvePreserve + ) => WidgetConfig + + expectTypeOf(makeConfig).toEqualTypeOf() + }) + + it('correctly merges a default config object with some related settings', () => { + interface TestTemplates extends BaseTemplateSettings { + testTemplate?: () => DOMContent + } + + interface TestClasses extends BaseClassesSettings { + testClass?: string + } + + interface TestSettings extends BaseWidgetSettings { + testSetting?: { + testNestedSetting?: { + anActualValue: boolean + + testDoubleNestedSetting?: { + andAnOptionalOne?: string + } + } + } + } + + const container = document.createElement('div') + const render: RenderingFunc = async (): Promise => 'Hello world' + + const defaults: DefaultWidgetConfig = { + testSetting: { + testNestedSetting: { + anActualValue: true, + testDoubleNestedSetting: { + andAnOptionalOne: 'now that is deep', + }, + }, + }, + classes: { + root: 'test--root', + testClass: 'test--testClass', + }, + templates: { + testTemplate: () => 'Hello, World!', + }, + } + + const settings: ResolvePreserve = { + container, + render, + testSetting: { + testNestedSetting: { + anActualValue: false, + testDoubleNestedSetting: {}, + }, + }, + classes: { + testClass: 'overridden-class', + }, + } + + const config = makeConfig(defaults, settings) + + // TODO: this assertion should not fail. makeConfig is "parameterized" with + // (...) => WidgetConfig ; I'd expect TSC to consider the return + // type to be WidgetConfig, but it resolves to WidgetConfig + // expectTypeOf(config).toEqualTypeOf>() + expectTypeOf(config).toEqualTypeOf>() + + // Resulting config is a merge of default config & settings, with precedence to the latter + expect(config).toEqual({ + ...defaults, + ...settings, + + testSetting: { + testNestedSetting: { + ...defaults.testSetting.testNestedSetting, + ...settings.testSetting?.testNestedSetting, + + testDoubleNestedSetting: { + ...defaults.testSetting.testNestedSetting.testDoubleNestedSetting, + ...settings.testSetting?.testNestedSetting?.testDoubleNestedSetting, + }, + }, + }, + + classes: { + ...defaults.classes, + ...settings.classes, + }, + + templates: { + ...defaults.templates, + ...settings.templates, + }, + }) + }) +}) From dcbcf634a752d6b34272500831c150e7d24875e7 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 19:42:27 +0100 Subject: [PATCH 11/14] refactor: alias `ResolvePreserve` to `SettingsLiteral` for improved semantics --- src/widgets/base.ts | 16 +++++++++++----- src/widgets/config.ts | 4 +++- test/config.test.ts | 7 ++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/widgets/base.ts b/src/widgets/base.ts index 6af3d36..1749a14 100644 --- a/src/widgets/base.ts +++ b/src/widgets/base.ts @@ -1,22 +1,28 @@ -import { DOMContent, ResolvePreserve } from '@/types' +import { DOMContent } from '@/types' import { Client } from '@alma/client' import { setDOMContent } from '@/utils' import { WidgetsController } from '@/widgets_controller' import { WidgetFactoryFunc } from './types' -import { DefaultWidgetConfig, makeConfig, WidgetConfig, WidgetSettings } from '@/widgets/config' +import { + DefaultWidgetConfig, + makeConfig, + SettingsLiteral, + WidgetConfig, + WidgetSettings, +} from '@/widgets/config' export type ConstructorFor = T extends Widget - ? new (almaClient: Client, settings: ResolvePreserve) => T + ? new (almaClient: Client, settings: SettingsLiteral) => T : never export type SettingsFor = T extends Widget - ? ResolvePreserve + ? SettingsLiteral : never export abstract class Widget { private readonly _config: WidgetConfig - constructor(protected readonly almaClient: Client, settings: ResolvePreserve) { + constructor(protected readonly almaClient: Client, settings: SettingsLiteral) { this._config = makeConfig(this.defaultConfig(), settings) } diff --git a/src/widgets/config.ts b/src/widgets/config.ts index d06ac40..f705ed6 100644 --- a/src/widgets/config.ts +++ b/src/widgets/config.ts @@ -29,6 +29,8 @@ export interface BaseWidgetSettings< export type WidgetSettings = BaseWidgetSettings +export type SettingsLiteral = ResolvePreserve + /* * *Config: "complete" representations of settings for internal use */ @@ -56,7 +58,7 @@ export type DefaultWidgetConfig = T extends BaseWidgetSettings( defaults: DefaultWidgetConfig, - settings: ResolvePreserve + settings: SettingsLiteral ): WidgetConfig { const result: WidgetConfig = {} as WidgetConfig const sources = [defaults, settings] diff --git a/test/config.test.ts b/test/config.test.ts index a65adb1..bca810d 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -6,17 +6,18 @@ import { BaseWidgetSettings, DefaultWidgetConfig, makeConfig, + SettingsLiteral, WidgetConfig, WidgetSettings, } from '@/widgets/config' -import { DOMContent, ResolvePreserve } from '../src/types' +import { DOMContent } from '../src/types' import { RenderingFunc } from '../src/widgets/types' describe('makeConfig', () => { it('has the correct signature', () => { type expectedFuncSignature = ( defaults: DefaultWidgetConfig, - settings: ResolvePreserve + settings: SettingsLiteral ) => WidgetConfig expectTypeOf(makeConfig).toEqualTypeOf() @@ -64,7 +65,7 @@ describe('makeConfig', () => { }, } - const settings: ResolvePreserve = { + const settings: SettingsLiteral = { container, render, testSetting: { From f9606b81e3c97d3496cc2c82ddef486e4f00c6b3 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 21:16:36 +0100 Subject: [PATCH 12/14] chore: adjust TS config to be able to test on older Node versions --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 49a4033..10f7291 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "moduleResolution": "node", - "target": "ESNext", - "module": "ESNext", + "target": "ES2019", + "module": "commonjs", "lib": ["es2015", "es2016", "es2017", "dom"], "strict": true, "sourceMap": true, From 439416e62860db7ebe604751c0ca1fa3b1cdb635 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 28 Nov 2020 23:00:54 +0100 Subject: [PATCH 13/14] refactor: pass settings type explicitly to `makeConfig` to solve type resolution issue --- src/widgets/base.ts | 2 +- test/config.test.ts | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/widgets/base.ts b/src/widgets/base.ts index 1749a14..fedf528 100644 --- a/src/widgets/base.ts +++ b/src/widgets/base.ts @@ -23,7 +23,7 @@ export abstract class Widget { private readonly _config: WidgetConfig constructor(protected readonly almaClient: Client, settings: SettingsLiteral) { - this._config = makeConfig(this.defaultConfig(), settings) + this._config = makeConfig(this.defaultConfig(), settings) } abstract defaultConfig(): DefaultWidgetConfig diff --git a/test/config.test.ts b/test/config.test.ts index bca810d..31dd03e 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -79,13 +79,8 @@ describe('makeConfig', () => { }, } - const config = makeConfig(defaults, settings) - - // TODO: this assertion should not fail. makeConfig is "parameterized" with - // (...) => WidgetConfig ; I'd expect TSC to consider the return - // type to be WidgetConfig, but it resolves to WidgetConfig - // expectTypeOf(config).toEqualTypeOf>() - expectTypeOf(config).toEqualTypeOf>() + const config = makeConfig(defaults, settings) + expectTypeOf(config).toEqualTypeOf>() // Resulting config is a merge of default config & settings, with precedence to the latter expect(config).toEqual({ From de8a80a281f9674483adf4c856f4e745b913fa62 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Mon, 30 Nov 2020 19:53:03 +0100 Subject: [PATCH 14/14] test(coverage): collect coverage but don't set a minimum threshold --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 4721ef0..03d1d4b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "scripts": { "start": "microbundle --name Alma watch", - "test": "jest --coverage=false", + "test": "jest --coverage", "test:no-coverage": "jest", "test:watch": "jest --coverage --watch", "test:prod": "npm run lint && npm run test -- --no-cache", @@ -198,14 +198,14 @@ ], "coverageThreshold": { "global": { - "branches": 90, - "functions": 95, - "lines": 95, - "statements": 95 + "branches": 0, + "functions": 0, + "lines": 0, + "statements": 0 } }, "collectCoverageFrom": [ - "src/*.{js,ts}" + "src/**/*.{js,ts}" ] }, "lint-staged": {