Basic outfit state Cypress tests

This commit is contained in:
Emi Matchu 2021-04-16 03:04:39 -07:00
parent 8d18bc06b7
commit 405e35a546
22 changed files with 910 additions and 54 deletions

View file

@ -1,3 +1,4 @@
{ {
"baseUrl": "http://localhost:3000" "baseUrl": "http://localhost:3000",
"ignoreTestFiles": ["**/__snapshots__/*", "**/__image_snapshots__/*"]
} }

View file

@ -0,0 +1,109 @@
// Give network requests a bit of breathing room!
const networkTimeout = { timeout: 12000 };
describe("WardrobePage: Basic outfit state", () => {
it("Initialize simple outfit from URL", () => {
cy.visit("/outfits/new?species=1&color=8&objects[]=76789");
getSpeciesSelect(networkTimeout)
.find(":selected")
.should("have.text", "Acara");
getColorSelect().find(":selected").should("have.text", "Blue");
cy.location().toMatchSnapshot();
cy.contains("A Warm Winters Night Background", networkTimeout).should(
"exist"
);
getOutfitPreview().toMatchImageSnapshot();
});
it("Changes species and color", () => {
cy.visit("/outfits/new?species=1&color=8&objects[]=76789");
getSpeciesSelect(networkTimeout)
.find(":selected")
.should("have.text", "Acara");
getColorSelect().find(":selected").should("have.text", "Blue");
cy.location().toMatchSnapshot();
getOutfitPreview().toMatchImageSnapshot();
getSpeciesSelect().select("Aisha");
getSpeciesSelect().find(":selected").should("have.text", "Aisha");
getColorSelect().find(":selected").should("have.text", "Blue");
cy.location().toMatchSnapshot();
getOutfitPreview().toMatchImageSnapshot();
getColorSelect().select("Red");
getSpeciesSelect().find(":selected").should("have.text", "Aisha");
getColorSelect().find(":selected").should("have.text", "Red");
cy.location().toMatchSnapshot();
getOutfitPreview().toMatchImageSnapshot();
});
it.only("Changes pose", () => {
cy.visit("/outfits/new?species=1&color=8&pose=HAPPY_FEM");
getPosePickerButton(networkTimeout).click();
getPosePickerOption("Happy and Feminine").should("be.checked");
cy.location().toMatchSnapshot();
getOutfitPreview().toMatchImageSnapshot();
getPosePickerOption("Sad and Masculine").check({ force: true });
getPosePickerOption("Sad and Masculine").should("be.checked");
cy.location().toMatchSnapshot();
getOutfitPreview().toMatchImageSnapshot();
});
it("Toggles item", () => {
cy.visit("/outfits/new?species=1&color=8&objects[]=76789");
getOutfitPreview().toMatchImageSnapshot();
cy.location().toMatchSnapshot();
cy.contains("A Warm Winters Night Background").click();
getOutfitPreview().toMatchImageSnapshot();
cy.location().toMatchSnapshot();
});
it("Renames outfit", () => {
cy.visit("/outfits/new?name=My+outfit&species=1&color=8");
getOutfitName(networkTimeout).should("have.text", "My outfit");
getOutfitName().click().type("Awesome outfit{enter}");
getOutfitName().should("have.text", "Awesome outfit");
cy.location().toMatchSnapshot();
});
});
function getSpeciesSelect(options) {
return cy.get("[data-test-id=wardrobe-species-picker]", options);
}
function getColorSelect(options) {
return cy.get("[data-test-id=wardrobe-color-picker]", options);
}
function getPosePickerButton(options) {
return cy.get("[data-test-id=wardrobe-pose-picker]", options);
}
function getPosePickerOption(label, options) {
return cy.get(`input[aria-label="${CSS.escape(label)}"]`, options);
}
function getOutfitPreview() {
return cy.get("[data-test-id=wardrobe-outfit-preview]:not([data-loading])", {
// A bit of an extra-long timeout, to await both server data and image data
timeout: 15000,
});
}
function getOutfitName(options) {
return cy.get("[data-test-id=outfit-name]", options);
}

View file

@ -1,10 +1,10 @@
// Give network requests a bit of breathing room! // Give network requests a bit of breathing room!
const networkTimeout = { timeout: 6000 }; const networkTimeout = { timeout: 10000 };
describe("WardrobePage: SearchPanel", () => { describe("WardrobePage: SearchPanel", () => {
// NOTE: This test depends on specific search results on certain pages, and // NOTE: This test depends on specific search results on certain pages, and
// could break if a lot of matching items are added to the site! // could break if a lot of matching items are added to the site!
it.only("Searches by keyword", () => { it("Searches by keyword", () => {
cy.visit("/outfits/new"); cy.visit("/outfits/new");
// The first page should contain this item. // The first page should contain this item.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -0,0 +1,127 @@
exports[`WardrobePage: Basic outfit state > Initialize simple outfit from URL #0`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=1&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=1&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Changes species and color #0`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=1&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=1&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Changes species and color #1`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=2&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=2&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Changes species and color #2`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=2&color=61&pose=HAPPY_FEM&objects%5B%5D=76789",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=2&color=61&pose=HAPPY_FEM&objects%5B%5D=76789",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Toggles item #0`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=1&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=1&color=8&pose=HAPPY_FEM&objects%5B%5D=76789",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Toggles item #1`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=1&color=8&pose=HAPPY_FEM&closet%5B%5D=76789",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=1&color=8&pose=HAPPY_FEM&closet%5B%5D=76789",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Changes pose #0`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=1&color=8&pose=HAPPY_FEM",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=1&color=8&pose=HAPPY_FEM",
"superDomain": "localhost"
};
exports[`WardrobePage: Basic outfit state > Changes pose #1`] =
{
"auth": "",
"hash": "",
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/outfits/new?name=&species=1&color=8&pose=SAD_MASC",
"origin": "http://localhost:3000",
"originPolicy": "http://localhost:3000",
"pathname": "/outfits/new",
"port": "3000",
"protocol": "http:",
"search": "?name=&species=1&color=8&pose=SAD_MASC",
"superDomain": "localhost"
};

View file

@ -1,21 +1,6 @@
/// <reference types="cypress" /> const { initPlugin } = require("cypress-plugin-snapshots/plugin");
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => { module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits initPlugin(on, config);
// `config` is the resolved Cypress config return config;
} };

View file

@ -1,25 +1 @@
// *********************************************** import "cypress-plugin-snapshots/commands";
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View file

@ -89,6 +89,7 @@
"apollo-server-testing": "^2.12.0", "apollo-server-testing": "^2.12.0",
"auth0": "^2.28.0", "auth0": "^2.28.0",
"cypress": "^6.4.0", "cypress": "^6.4.0",
"cypress-plugin-snapshots": "^1.4.4",
"dotenv-cli": "^3.1.0", "dotenv-cli": "^3.1.0",
"es6-promise-pool": "^2.5.0", "es6-promise-pool": "^2.5.0",
"inquirer": "^7.3.3", "inquirer": "^7.3.3",

View file

@ -271,7 +271,7 @@ function OutfitHeading({ outfitState, dispatchToOutfit }) {
<Box> <Box>
<Box role="group" d="inline-block" position="relative" width="100%"> <Box role="group" d="inline-block" position="relative" width="100%">
<Heading1> <Heading1>
<EditablePreview lineHeight="48px" /> <EditablePreview lineHeight="48px" data-test-id="outfit-name" />
<EditableInput lineHeight="48px" /> <EditableInput lineHeight="48px" />
</Heading1> </Heading1>
</Box> </Box>

View file

@ -197,6 +197,12 @@ function OutfitControls({
idealPose={outfitState.pose} idealPose={outfitState.pose}
onChange={onSpeciesColorChange} onChange={onSpeciesColorChange}
stateMustAlwaysBeValid stateMustAlwaysBeValid
speciesPickerProps={{
"data-test-id": "wardrobe-species-picker",
}}
colorPickerProps={{
"data-test-id": "wardrobe-color-picker",
}}
/> />
} }
</DarkMode> </DarkMode>
@ -210,6 +216,7 @@ function OutfitControls({
dispatchToOutfit={dispatchToOutfit} dispatchToOutfit={dispatchToOutfit}
onLockFocus={onLockFocus} onLockFocus={onLockFocus}
onUnlockFocus={onUnlockFocus} onUnlockFocus={onUnlockFocus}
data-test-id="wardrobe-pose-picker"
/> />
</Flex> </Flex>
</Flex> </Flex>

View file

@ -63,6 +63,7 @@ function PosePicker({
dispatchToOutfit, dispatchToOutfit,
onLockFocus, onLockFocus,
onUnlockFocus, onUnlockFocus,
...props
}) { }) {
const theme = useTheme(); const theme = useTheme();
const initialFocusRef = React.useRef(); const initialFocusRef = React.useRef();
@ -193,6 +194,7 @@ function PosePicker({
`, `,
isOpen && "is-open" isOpen && "is-open"
)} )}
{...props}
> >
<EmojiImage src={getIcon(pose)} alt="Choose a pose" /> <EmojiImage src={getIcon(pose)} alt="Choose a pose" />
</Button> </Button>

View file

@ -30,6 +30,7 @@ function WardrobePreviewAndControls({
wornItemIds: outfitState.wornItemIds, wornItemIds: outfitState.wornItemIds,
onChangeHasAnimations: setHasAnimations, onChangeHasAnimations: setHasAnimations,
backdrop: <OutfitThumbnailIfCached outfitId={outfitState.id} />, backdrop: <OutfitThumbnailIfCached outfitId={outfitState.id} />,
"data-test-id": "wardrobe-outfit-preview",
}); });
return ( return (

View file

@ -52,6 +52,7 @@ export function useOutfitPreview({
loadingDelayMs, loadingDelayMs,
spinnerVariant, spinnerVariant,
onChangeHasAnimations = null, onChangeHasAnimations = null,
...props
}) { }) {
const appearance = useOutfitAppearance({ const appearance = useOutfitAppearance({
speciesId, speciesId,
@ -102,6 +103,7 @@ export function useOutfitPreview({
onChangeHasAnimations={onChangeHasAnimations} onChangeHasAnimations={onChangeHasAnimations}
doTransitions doTransitions
isPaused={isPaused} isPaused={isPaused}
{...props}
/> />
); );
} }
@ -122,6 +124,7 @@ export function OutfitLayers({
spinnerVariant = "overlay", spinnerVariant = "overlay",
doTransitions = false, doTransitions = false,
isPaused = true, isPaused = true,
...props
}) { }) {
const containerRef = React.useRef(null); const containerRef = React.useRef(null);
const [canvasSize, setCanvasSize] = React.useState(0); const [canvasSize, setCanvasSize] = React.useState(0);
@ -178,6 +181,8 @@ export function OutfitLayers({
// Create a stacking context, so the z-indexed layers don't escape! // Create a stacking context, so the z-indexed layers don't escape!
zIndex="0" zIndex="0"
ref={containerRef} ref={containerRef}
data-loading={loading ? true : undefined}
{...props}
> >
{backdrop && ( {backdrop && (
<FullScreenCenter> <FullScreenCenter>

View file

@ -27,6 +27,8 @@ function SpeciesColorPicker({
isDisabled = false, isDisabled = false,
speciesIsDisabled = false, speciesIsDisabled = false,
size = "md", size = "md",
speciesPickerProps = {},
colorPickerProps = {},
onChange, onChange,
}) { }) {
const { loading: loadingMeta, error: errorMeta, data: meta } = useQuery(gql` const { loading: loadingMeta, error: errorMeta, data: meta } = useQuery(gql`
@ -188,6 +190,7 @@ function SpeciesColorPicker({
valids={valids} valids={valids}
speciesId={speciesId} speciesId={speciesId}
colorId={colorId} colorId={colorId}
{...colorPickerProps}
> >
{ {
// If the selected color isn't in the set we have here, show the // If the selected color isn't in the set we have here, show the
@ -231,6 +234,7 @@ function SpeciesColorPicker({
valids={valids} valids={valids}
speciesId={speciesId} speciesId={speciesId}
colorId={colorId} colorId={colorId}
{...speciesPickerProps}
> >
{ {
// If the selected species isn't in the set we have here, show the // If the selected species isn't in the set we have here, show the

650
yarn.lock

File diff suppressed because it is too large Load diff