diff --git a/.circleci/config.yml b/.circleci/config.yml index dd0d77bc6..1c16fe79b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,6 +8,10 @@ deploy_defaults: &deploy_defaults docker: - image: cimg/python:3.10.2 +test_defaults: &test_defaults + docker: + - image: cypress/browsers:node16.14.2-slim-chrome100-ff99-edge + install_build_dependency: &install_build_dependency name: Installation of build and deployment dependencies. command: | @@ -50,6 +54,14 @@ running_yarn_build: &running_yarn_build yarn install yarn build +running_yarn_test: &running_yarn_test + name: Running Yarn Test Build + command: | + yarn install + yarn cypress install + yarn build + yarn cy:ci + workspace_persist: &workspace_persist root: . paths: @@ -81,6 +93,27 @@ build_steps: &build_steps - run: *running_yarn_build - persist_to_workspace: *workspace_persist +test_steps: &test_steps + # Initialization. + - checkout + - setup_remote_docker + - restore_cache: + key: test-node-modules-{{ checksum "yarn.lock" }} + - run: *running_yarn_test + - save_cache: + key: test-node-modules-{{ checksum "yarn.lock" }} + paths: + - node_modules + - /root/.cache/Cypress + - store_test_results: + path: cypress/test-report + - store_artifacts: + path: cypress/test-report + - store_artifacts: + path: cypress/videos + - store_artifacts: + path: cypress/screenshots + deploy_steps: &deploy_steps - checkout - attach_workspace: *workspace_attach @@ -127,6 +160,14 @@ jobs: LOGICAL_ENV: "prod" APPNAME: "platform-ui-mvp" steps: *build_steps + + test-dev: + <<: *test_defaults + environment: + DEPLOY_ENV: "DEV" + LOGICAL_ENV: "dev" + APPNAME: "platform-ui-mvp" + steps: *test_steps # Just tests commited code. deployDev: @@ -147,35 +188,6 @@ jobs: APPNAME: "platform-ui-mvp" steps: *deploy_steps - # Test job for the cases when we don not need deployment. - e2e-test: - docker: - - image: cypress/browsers:node16.14.2-slim-chrome100-ff99-edge - steps: - - checkout - - restore_cache: - key: test-node-modules-{{ checksum "yarn.lock" }} - - run: - name: Config Git - command: git config --global url."https://git@".insteadOf git:// - - run: - name: Install Dependencies - command: yarn install - no_output_timeout: 20m - - run: - name: Install Cypress Binary - command: yarn cypress install - - run: - name: Build the application - command: yarn build - no_output_timeout: 20m - - save_cache: - key: test-node-modules-{{ checksum "yarn.lock" }} - paths: - - node_modules - - /root/.cache/Cypress - - run: yarn cy:ci - workflows: version: 2 build: @@ -201,9 +213,6 @@ workflows: ignore: - master - - e2e-test: - context : org-global - - build-prod: context : org-global filters: @@ -215,7 +224,6 @@ workflows: context : org-global requires: - build-dev - - e2e-test filters: branches: only: @@ -229,3 +237,6 @@ workflows: branches: only: - master + + - test-dev: + context : org-global \ No newline at end of file diff --git a/.gitignore b/.gitignore index 63c983693..6d23f4087 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ # testing /coverage .nyc_output +/cypress/screenshots +/cypress/videos +/cypress/test-report # production /build diff --git a/README.md b/README.md index 8e34fdd33..f3d7134a8 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,11 @@ See the [Dev Center README](/src-ts/tools/dev-center/README.md) for further inst | `yarn eslint` | Run eslint against js/x files and outputs report | | `yarn eslint:fix` | Run eslint against js/x files, fixes auto-fixable issues, and outputs report | | `yarn test` | Run unit tests, watching for changes and re-running per your specifications | -| `yarn test:no-watch` | Run unit tests once, without watching for changes or re-running | +| `yarn test:no-watch` | Run unit tests once, without watching for changes or re-running | +| `yarn cy:run` | Run e2e tests once in local command with the site is running | +| `yarn cy:ci` | Run e2e tests once by circle ci | +| `yarn report:coverage`| Generate e2e coverage report in html format | +| `yarn report:coverage:text` | Generate e2e coverage report in text format | ## Folder Structure diff --git a/cypress.config.ts b/cypress.config.ts index e622ea360..fc55f43d8 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,19 +1,33 @@ +// tslint:disable-next-line: no-submodule-imports This is the way cypress does it +import task from '@cypress/code-coverage/task' import { defineConfig } from 'cypress' export default defineConfig({ - fixturesFolder: false, - video: false, - screenshotOnRunFailure: false, - defaultCommandTimeout: 10000, - e2e: { - baseUrl: 'http://localhost:3000', - specPattern: "cypress/e2e/**/*.spec.{js,jsx,ts,tsx}", - supportFile: "cypress/support/e2e.ts", - viewportHeight: 1000, - viewportWidth: 1280, - setupNodeEvents(on, config) { - require('@cypress/code-coverage/task')(on, config) - return config; + defaultCommandTimeout: 10000, + e2e: { + // baseUrl: 'https://local.topcoder-dev.com', + baseUrl: 'http://localhost:3000', + setupNodeEvents: setUpNodeEvents, + specPattern: 'cypress/e2e/**/*.spec.{js,jsx,ts,tsx}', + supportFile: 'cypress/support/e2e.ts', + viewportHeight: 1000, + viewportWidth: 1280, }, - }, + fixturesFolder: false, + reporter: 'junit', + reporterOptions: { + mochaFile: 'cypress/test-report/test-result-[hash].xml', + toConsole: false, + }, + screenshotOnRunFailure: true, + video: true, }) + +// adds the config to node setup events +function setUpNodeEvents( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): Cypress.PluginConfigOptions { + task(on, config) + return config +} diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 72bc22b1b..43c584180 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -1,6 +1,12 @@ describe('Landing Page', () => { - beforeEach(() => cy.visit('/')) - it('loads landing page should be successfully', () => { - cy.get('[data-cy="root"]').should('be.visible') - }) + + beforeEach(() => cy.visit('/')) + + it('loads landing page should be successfully', () => { + cy.get('[data-id="root"]').should('be.visible') + }) + + it.skip('loads landing page should fail', () => { + cy.get('[data-id="root"]').should('not.be.visible') + }) }) diff --git a/package.json b/package.json index bf9628928..d17a1b4b5 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "eslint:fix": "eslint 'src/**/*.{js,jsx}' --fix", "test": "react-scripts test --watchAll", "test:no-watch": "react-scripts test --watchAll=false --passWithNoTests", - "cy:run": "cypress run", + "cy:run": "cypress run --reporter junit", "cy:ci": "start-server-and-test 'serve -s build -n -p 3000' http://localhost:3000 'cy:run'", "report:coverage": "nyc report --reporter=html", "report:coverage:text": "nyc report --reporter=text" @@ -23,9 +23,6 @@ "dependencies": { "@datadog/browser-logs": "^4.7.1", "@heroicons/react": "^1.0.6", - "@types/dompurify": "^2.3.3", - "@types/highlightjs": "^9.12.2", - "@types/marked": "4.0.3", "apexcharts": "^3.35.3", "axios": "^0.26.1", "browser-cookies": "^1.2.0", @@ -82,8 +79,12 @@ "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.2.1", "@types/axios": "^0.14.0", + "@types/cypress": "^1.1.3", + "@types/dompurify": "^2.3.3", + "@types/highlightjs": "^9.12.2", "@types/jest": "^27.0.1", "@types/lodash": "^4.14.182", + "@types/marked": "4.0.3", "@types/node": "^18.7.13", "@types/reach__router": "^1.3.10", "@types/react": "^18.0.5", @@ -94,6 +95,7 @@ "@types/segment-analytics": "^0.0.34", "@types/systemjs": "^6.1.0", "@types/uuid": "^8.3.4", + "@wdio/junit-reporter": "^7.24.0", "autoprefixer": "^9.8.6", "babel-eslint": "^11.0.0-beta.2", "babel-jest": "^24.9.0", diff --git a/public/index.html b/public/index.html index 35c55bd1e..da3a59318 100644 --- a/public/index.html +++ b/public/index.html @@ -32,7 +32,7 @@ -
+
- \ No newline at end of file + diff --git a/src-ts/declarations.d.ts b/src-ts/declarations.d.ts index bd7a8df0c..84ecabc9c 100644 --- a/src-ts/declarations.d.ts +++ b/src-ts/declarations.d.ts @@ -1,3 +1,5 @@ +declare module '@cypress/code-coverage/task' + declare module '*.html' { const htmlFile: string export = htmlFile diff --git a/src-ts/lib/form/form-functions/form.functions.ts b/src-ts/lib/form/form-functions/form.functions.ts index 5f5e72246..b2ec0c417 100644 --- a/src-ts/lib/form/form-functions/form.functions.ts +++ b/src-ts/lib/form/form-functions/form.functions.ts @@ -33,9 +33,13 @@ export function initializeValues(inputs: Array, formValues?: inputs .filter(input => !input.dirty && !input.touched) .forEach(input => { - input.value = !!(formValues as any)?.hasOwnProperty(input.name) - ? (formValues as any)[input.name] - : undefined + if (input.type === 'checkbox') { + input.value = input.checked || false + } else { + input.value = !!(formValues as any)?.hasOwnProperty(input.name) + ? (formValues as any)[input.name] + : undefined + } }) } @@ -121,7 +125,13 @@ function handleFieldEvent(input: HTMLInputElement | HTMLTextAreaElement, inpu inputDef.touched = true // set the def value - inputDef.value = input.value + if (input.type === 'checkbox') { + const checkbox: HTMLInputElement = input as HTMLInputElement + inputDef.value = checkbox.checked + inputDef.checked = checkbox.checked + } else { + inputDef.value = input.value + } // now let's validate the field const formElements: HTMLFormControlsCollection = (input.form as HTMLFormElement).elements diff --git a/src-ts/lib/form/form-groups/FormGroups.tsx b/src-ts/lib/form/form-groups/FormGroups.tsx index 2562d0146..141bcb4f5 100644 --- a/src-ts/lib/form/form-groups/FormGroups.tsx +++ b/src-ts/lib/form/form-groups/FormGroups.tsx @@ -23,12 +23,13 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr const { formDef, onBlur, onChange }: FormGroupsProps = props - const getTabIndex: (input: FormInputModel, index: number) => number = (input, index) => { + function getTabIndex(input: FormInputModel, index: number): number { const tabIndex: number = input.notTabbable ? -1 : index + 1 + (formDef.tabIndexStart || 0) return tabIndex } - const renderInputField: (input: FormInputModel, index: number) => JSX.Element | undefined = (input, index) => { + function renderInputField(input: FormInputModel, index: number): JSX.Element | undefined { + const tabIndex: number = getTabIndex(input, index) let inputElement: JSX.Element @@ -40,11 +41,10 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr {...input} onChange={onChange} tabIndex={tabIndex} - value={input.value} + value={input.value as number | undefined} /> ) break - case 'textarea': inputElement = ( JSX.Element = (props: FormGroupsPr onBlur={onBlur} onChange={onChange} tabIndex={tabIndex} - value={input.value} + value={input.value as string | undefined} /> ) break case 'checkbox': + inputElement = ( + + ) + break case 'radio': inputElement = ( JSX.Element = (props: FormGroupsPr onChange={onChange} tabIndex={tabIndex} type={input.type as InputTextTypes || 'text'} - value={input.value} + value={input.value as string | undefined} /> ) break @@ -100,9 +111,18 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr ) } - const formGroups: Array = formDef?.groups?.map((element: FormGroup, index: number) => { - return - }) || [] + const formGroups: Array = formDef?.groups + ?.map((element: FormGroup, index: number) => { + return ( + + ) + }) + || [] return (
diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss b/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss index 7b47d447d..ac7f33476 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss @@ -30,9 +30,32 @@ } &.checkbox { - & { - width: 20px; - height: 20px; + @extend .body-small; + color: $black-60; + box-sizing: border-box; + border: 0; + width: 100%; + padding: 0; + margin: 0; + height: auto; + border-radius: 0; + + &:focus { + box-shadow: none; + border: none; + outline: none; + color: $black-100; + } + + &:disabled { + background-color: $black-10; + } + + &.checkbox { + & { + width: 20px; + height: 20px; + } } } } diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx index 21e94e307..de7cd32e6 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx @@ -10,6 +10,7 @@ export type InputTextTypes = 'checkbox' | 'password' | 'text' interface InputTextProps { readonly autocomplete?: FormInputAutocompleteOption + readonly checked?: boolean readonly className?: string readonly dirty?: boolean readonly disabled?: boolean @@ -24,11 +25,15 @@ interface InputTextProps { readonly spellCheck?: boolean readonly tabIndex: number readonly type: InputTextTypes - readonly value?: string | number + readonly value?: string | number | boolean } const InputText: FC = (props: InputTextProps) => { + const defaultValue: string | number | undefined = props.type === 'checkbox' && !!props.checked + ? 'on' + : props.value as string | number | undefined + return ( = (props: InputTextProps) => { > + checked?: boolean readonly className?: string readonly dependentFields?: Array dirty?: boolean @@ -40,5 +41,5 @@ export interface FormInputModel { touched?: boolean readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' readonly validators?: ReadonlyArray - value?: string + value?: string | boolean } diff --git a/src-ts/lib/functions/authentication-functions/authentication.functions.test.ts b/src-ts/lib/functions/authentication-functions/authentication.functions.test.ts deleted file mode 100644 index 8d3b4e806..000000000 --- a/src-ts/lib/functions/authentication-functions/authentication.functions.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@testing-library/jest-dom' - -describe('Authentication Functions', () => { - - test('authentication', () => { }) -}) diff --git a/src-ts/lib/functions/user-functions/user.functions.test.ts b/src-ts/lib/functions/user-functions/user.functions.test.ts deleted file mode 100644 index c77886f77..000000000 --- a/src-ts/lib/functions/user-functions/user.functions.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import '@testing-library/jest-dom' - -describe('Profile Functions', () => { - - test('TODO', () => { - - }) -}) diff --git a/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/MarkdownDoc.module.scss b/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/MarkdownDoc.module.scss index e8e157f78..e3ddad865 100644 --- a/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/MarkdownDoc.module.scss +++ b/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/MarkdownDoc.module.scss @@ -16,7 +16,7 @@ @include font-weight-normal; font-size: 14px; line-height: 18px; - color: $purple-120; + color: $silver-2; word-break: break-word; } diff --git a/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/TableOfContents.tsx b/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/TableOfContents.tsx index 28d1adcb9..20a674635 100644 --- a/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/TableOfContents.tsx +++ b/src-ts/tools/dev-center/dev-center-lib/MarkdownDoc/TableOfContents.tsx @@ -14,9 +14,11 @@ export const TableOfContents: React.FC = (props) => { ] = React.useState(-1) const { toc }: { toc: TOC } = props const items: TOC = React.useMemo(() => { - return toc.filter((item) => (item.level === 2 || item.level === 3)) + return toc.filter((item) => item.level === 2 || item.level === 3) }, [toc]) + const navRef: React.RefObject = React.createRef() + const findActiveIndex: () => void = React.useCallback(() => { for (let i: number = 0; i < items.length; i++) { const h: HTMLElement | null = document.getElementById( @@ -29,27 +31,40 @@ export const TableOfContents: React.FC = (props) => { document.documentElement.clientHeight / 2 ) { setActiveIndex(i) + const liNodes: NodeListOf | undefined = + navRef.current?.querySelectorAll('li') + if (navRef.current && liNodes) { + navRef.current.scrollTop = + liNodes[i].offsetTop > + document.documentElement.clientHeight - 100 + ? liNodes[liNodes.length - 1].offsetTop + : 0 + } } } - }, [items]) + }, [items, navRef]) useOnScroll({ onScroll: findActiveIndex }) return ( -