Skip to content

Commit

Permalink
Enable cypress for v8
Browse files Browse the repository at this point in the history
  • Loading branch information
ecraig12345 committed Feb 9, 2022
1 parent 96d9c93 commit d1c59d2
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 55 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
// Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the file.exclude setting.
"search.exclude": {
".yarn/releases": true,
"common/temp": true,
"**/node_modules": true,
"**/lib": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Add ./e2e to list of files allowing devDependencies",
"packageName": "@fluentui/eslint-plugin",
"email": "[email protected]",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Add command to run e2e tests",
"packageName": "@fluentui/react",
"email": "[email protected]",
"dependentChangeType": "none"
}
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/utils/configHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const testFiles = [
'**/{test,tests}/**',
'**/testUtilities.{ts,tsx}',
'**/common/{isConformant,snapshotSerializers}.{ts,tsx}',
'./e2e/**',
];

const docsFiles = ['**/*Page.tsx', '**/{docs,demo}/**', '**/*.doc.{ts,tsx}'];
Expand Down
79 changes: 29 additions & 50 deletions packages/react-examples/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { withPerformance } from 'storybook-addon-performance';
import { withKeytipLayer, withStrictMode } from '@fluentui/storybook';

/**
* "PACKAGE_NAME" placeholder is being replaced by webpack loader - @link {./preview.loader}
* This placeholder will be replaced with the actual package name by custom webpack loader `./preview-loader.js`
* @type {string}
*/
const packageNamePlaceholder = 'PACKAGE_NAME';
Expand Down Expand Up @@ -36,16 +36,18 @@ export const parameters = {};
* default: { title: string };
* [subStoryName: string]: React.FunctionComponent | { title: string };
* }} Story
*/

/**
*
* @typedef {{ [exportName: string]: React.ComponentType }} ComponentModule
*/

/** */
function loadStories() {
/** @type {Map<string, Story>} */
const stories = new Map();

// This shows some extra e2e-only stories
const includeE2E = new URLSearchParams(location.search).has('e2e');

/** @type {__WebpackModuleApi.RequireContext[]} */
const contexts = [
// This will be updated by preview-loader with the actual current package name
Expand All @@ -62,7 +64,9 @@ function loadStories() {

for (const req of contexts) {
req.keys().forEach(key => {
generateStoriesFromExamples(key, stories, req);
if (includeE2E || !key.includes('/e2e/')) {
generateStoriesFromExamples(key, stories, req);
}
});
}

Expand All @@ -81,71 +85,46 @@ function loadStories() {
function generateStoriesFromExamples(key, stories, req) {
// Depending on the starting point of the context, and the package layout, the key will be like one of these:
// ./ComponentName/ComponentName.Something.Example.tsx
// ./ComponentName/e2e/ComponentName.Something.stories.tsx
// ./package-name/ComponentName/ComponentName.Something.Example.tsx
// ./package-name/src/.../ComponentName.stories.tsx - @TODO remove this line after new storybook setup has been applied for all converged packages
const segments = key.split('/');

if (segments.length < 3) {
console.warn(`Invalid storybook context location found: key: ${key} | segments: ${segments}`);
// ./package-name/ComponentName/e2e/ComponentName.Something.Example.tsx
// group 1: component name
// group 2: /e2e part (if present)
// group 3: story name
const pathParts = key.match(/\/(\w+)(\/e2e)?\/[\w.]+$/);
if (!pathParts) {
console.error(`Invalid path found in storybook require.context: "${key}"`);
return;
}

const componentName = generateComponentName(segments);
const [, componentName, e2e = ''] = pathParts;
// This will be like either:
// Components/ComponentName
// Components/ComponentName/e2e
const componentPath = `Components/${componentName}${e2e}`;

if (!stories.has(componentName)) {
stories.set(componentName, {
if (!stories.has(componentPath)) {
stories.set(componentPath, {
default: {
title: 'Components/' + componentName,
title: componentPath,
},
});
}

const storyName = segments.slice(-1)[0].replace('.tsx', '').replace(/\./g, '_');

const story = stories.get(componentName);
const story = /** @type {Story} */ (stories.get(componentPath));
const exampleModule = /** @type {(key: string) => ComponentModule} */ (req)(key);

if (!story) {
console.warn(`No stories for component: ${componentName}`);
return;
}

for (let moduleExport of Object.keys(exampleModule)) {
const ExampleComponent = exampleModule[moduleExport];
const subStoryName = moduleExport || storyName;
for (const [moduleExport, ExampleComponent] of Object.entries(exampleModule)) {
const subStoryName = moduleExport.replace(componentName, '').replace(/Example$/, '');

if (typeof ExampleComponent === 'function') {
if (ExampleComponent.prototype.render) {
// class component
// class component -- make a wrapper function component
story[subStoryName] = () => React.createElement(ExampleComponent);
} else {
// function component
story[subStoryName] = /** @type {React.FunctionComponent} */ (ExampleComponent);
}
}
}

/**
*
* @param {string[]} segments
* @returns {string} component name
*/
function generateComponentName(segments) {
/**
* ./ComponentName/ComponentName.Something.Example.tsx
*/
const isReactExamplesStory = segments.length === 3;

if (isReactExamplesStory) {
// ./ComponentName/ComponentName.Something.Example.tsx
// ↓↓↓
// [., ComponentName, ComponentName.Something.Example.tsx]
return segments[1];
}

// .package-name/ComponentName/ComponentName.Something.Example.tsx
// ↓↓↓
// [., package-name, ComponentName, ComponentName.Something.Example.tsx]
return segments[2];
}
}
2 changes: 2 additions & 0 deletions packages/react-examples/e2e/support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// workaround for https://github.com/cypress-io/cypress/issues/8599
import '@fluentui/scripts/cypress/support';
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';
import { FocusZone, FocusTrapZone, FocusZoneDirection, mergeStyles } from '@fluentui/react';

const rootClass = mergeStyles({
position: 'relative',
button: { position: 'absolute', height: 30, width: 30 },
'#a': { top: 0, left: 0 },
'#b': { top: 0, left: 30 },
'#c': { top: 0, left: 60 },
'#d': { top: 30, left: 0 },
'#e': { top: 30, left: 30 },
'#f': { top: 30, left: 60 },
});

/**
* Tab and shift-tab wrap at extreme ends of the FTZ:
*
* can tab across FocusZones with different button structures
*/
export const TabWrappingMultiFocusZone = () => {
return (
<div className={rootClass}>
<FocusTrapZone forceFocusInsideTrap={false}>
<FocusZone direction={FocusZoneDirection.horizontal}>
<div>
<button id="a">a</button>
</div>
<div>
<button id="b">b</button>
</div>
<div>
<button id="c">c</button>
</div>
</FocusZone>
<FocusZone direction={FocusZoneDirection.horizontal}>
<div>
<div>
<button id="d">d</button>
<button id="e">e</button>
<button id="f">f</button>
</div>
</div>
</FocusZone>
</FocusTrapZone>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const ftzStoriesTitle = 'Components/FocusTrapZone/e2e';

describe('FocusTrapZone', () => {
before(() => {
cy.visitStorybook({ qs: { e2e: '1' } });
});

describe('Tab and shift-tab wrap at extreme ends of the FTZ', () => {
it('can tab across FocusZones with different button structures', () => {
cy.loadStory(ftzStoriesTitle, 'TabWrappingMultiFocusZone');

cy.get('#a').focus();
cy.focused().should('have.id', 'a');

// shift+tab to focus first bumper
cy.realPress(['Shift', 'Tab']);
cy.focused().should('have.id', 'd');

// tab to focus last bumper
cy.realPress('Tab');
cy.focused().should('have.id', 'a');
});

it('can tab across a FocusZone with different button structures', () => {
cy.loadStory(ftzStoriesTitle, 'TabWrappingFocusZone');

cy.get('#x').focus();
cy.focused().should('have.id', 'x');

// shift+tab to focus first bumper
cy.realPress(['Shift', 'Tab']);
cy.focused().should('have.id', 'a');

// tab to focus last bumper
cy.realPress('Tab');
cy.focused().should('have.id', 'x');
});

it(
'can trap focus when FTZ bookmark elements are FocusZones, ' +
'and those elements have inner elements focused that are not the first inner element',
() => {
cy.loadStory(ftzStoriesTitle, 'TabWrappingFocusZoneBumpers');

// Focus the middle button in the first FZ.
cy.get('#a').focus().realPress('ArrowRight');
cy.focused().should('have.id', 'b');

// Focus the middle button in the second FZ.
cy.get('#e').focus().realPress('ArrowRight');
cy.focused().should('have.id', 'f');

// tab to focus last bumper
cy.realPress('Tab');
cy.focused().should('have.id', 'b');

// shift+tab to focus first bumper
cy.realPress(['Shift', 'Tab']);
cy.focused().should('have.id', 'f');
},
);
});
});
2 changes: 1 addition & 1 deletion packages/react-examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"skipLibCheck": true,
"lib": ["es5", "dom", "es2015.promise"],
"typeRoots": ["../../node_modules/@types", "../../typings"],
"types": ["webpack-env", "custom-global", "node"],
"types": ["webpack-env", "custom-global", "node", "cypress", "cypress-storybook/cypress", "cypress-real-events"],
"paths": {
"@fluentui/react-examples/lib/*": ["./src/*"],
"@fluentui/react-examples": ["./src"]
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"clean": "just-scripts clean",
"code-style": "just-scripts code-style",
"codepen": "node ../../scripts/local-codepen.js",
"e2e": "yarn workspace @fluentui/react-examples e2e --package react",
"just": "just-scripts",
"lint": "just-scripts lint",
"start": "cross-env NODE_OPTIONS=--max-old-space-size=3072 just-scripts dev:storybook",
Expand Down
12 changes: 8 additions & 4 deletions scripts/cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ const argv = require('yargs')
describe: 'Choose a mode to run cypress',
choices: ['run', 'open'],
})
.option('package', {
describe: 'Package to load the deployed storybook for (used by PR runs only)',
default: 'react-components',
type: 'string',
})
.option('port', {
describe: 'Port number storybook is running on',
describe: 'Port number storybook is running on (used by local runs only)',
default: 3000,
type: 'number',
})
.demandOption('mode').argv;

const baseConfig = {
baseUrl: process.env.DEPLOYURL
? // Base path hard coded for converged for now, can be modified to be configurable if required to other projects
`${process.env.DEPLOYURL}/react-components/storybook`
: `http://localhost:${argv.port}`,
? `${process.env.DEPLOYURL}/${argv.package}/storybook?e2e=1`
: `http://localhost:${argv.port}?e2e=1`,
fixturesFolder: path.join(__dirname, 'cypress/fixtures'),
integrationFolder: '.',
pluginsFile: path.join(__dirname, 'cypress/plugins/index.js'),
Expand Down

0 comments on commit d1c59d2

Please sign in to comment.