diff --git a/cypress/.gitignore b/cypress/.gitignore new file mode 100644 index 0000000..de68fb1 --- /dev/null +++ b/cypress/.gitignore @@ -0,0 +1 @@ +screenshots diff --git a/src/components/ColorValues/HexEditorTest.cy.tsx b/src/components/ColorValues/HexEditorTest.cy.tsx new file mode 100644 index 0000000..7db6f55 --- /dev/null +++ b/src/components/ColorValues/HexEditorTest.cy.tsx @@ -0,0 +1,84 @@ +import { useReducer } from "react"; + +import { Color } from "colorlib"; + +import { colorReducer, createColorActions } from "@hooks/color"; + +import { HexEditor } from "./ValueEditor"; + +const initialState = { + color: Color.from_hex("000"), +}; + +function TestWrapper() { + const [state, dispatch] = useReducer(colorReducer, initialState); + const actions = createColorActions(dispatch); + + return ( +
+ +

+ Color: #{state.color.hex.to_code()} +

+
+ ); +} + +describe("hex editor tests", () => { + it("edits the hex value", () => { + cy.mount(); + + // Verify initial state + cy.dataCy("current-color").as("color").should("have.text", "000000"); + + // Should select text on focus + cy.dataCy("hex-value-input") + .as("value") + .focus() + .should("have.prop", "selectionStart", 0) + .should("have.prop", "selectionEnd") + .should("be.gt", 0); + + // Hex short code should be maintained while editing + cy.get("@value").type("{rightArrow}").type("{backspace}"); + cy.get("@value").should("have.value", "#00000"); + cy.get("@color").should("have.text", "000000"); + + cy.get("@value").type("{backspace}").type("{backspace}"); + cy.get("@value").should("have.value", "#000"); + cy.get("@color").should("have.text", "000000"); + + cy.get("@value").blur(); + cy.get("@value").should("have.value", "#000000"); + cy.get("@color").should("have.text", "000000"); + + // Type a new value + cy.get("@value").focus().type("{backspace}"); + cy.get("@value").should("have.value", ""); + cy.get("@color").should("have.text", "000000"); + + cy.get("@value").type("ab"); + cy.get("@value").should("have.value", "ab"); + cy.get("@color").should("have.text", "000000"); + + cy.get("@value").type("c"); + cy.get("@value").should("have.value", "#ABC"); + cy.get("@color").should("have.text", "AABBCC"); + + cy.get("@value").blur(); + cy.get("@value").should("have.value", "#AABBCC"); + cy.get("@color").should("have.text", "AABBCC"); + + // Invalid blur resets to last valid color + cy.get("@value").focus().type("Invalid"); + cy.get("@value").blur(); + cy.get("@value").should("have.value", "#AABBCC"); + cy.get("@color").should("have.text", "AABBCC"); + + // Escape blurs input + cy.get("@value").focus(); + cy.get("@value").should("have.focus"); + cy.get("@value").type("{esc}"); + cy.get("@value").should("not.have.focus"); + }); +}); diff --git a/src/components/ColorValues/SpaceEditorTest.cy.tsx b/src/components/ColorValues/SpaceEditorTest.cy.tsx new file mode 100644 index 0000000..166f62f --- /dev/null +++ b/src/components/ColorValues/SpaceEditorTest.cy.tsx @@ -0,0 +1,105 @@ +import { useReducer } from "react"; + +import { Color } from "colorlib"; + +import { roundTo } from "@/util"; +import { colorReducer, createColorActions } from "@hooks/color"; + +import SpaceEditor from "./SpaceEditor"; + +const initialState = { + color: Color.from_hex("2edd9d"), +}; + +function TestWrapper() { + const [state, dispatch] = useReducer(colorReducer, initialState); + const actions = createColorActions(dispatch); + + return ( + <> +
+ + + +
+ +
+

+ HCL ({roundTo(state.color.hcl.h, 0)}, {roundTo(state.color.hcl.c, 2)},{" "} + {roundTo(state.color.hcl.l, 2)}) +

+

+ HSV ({roundTo(state.color.hsv.h, 0)}, {roundTo(state.color.hsv.s, 2)},{" "} + {roundTo(state.color.hsv.v, 2)}) +

+

+ RGB ({roundTo(state.color.rgb.r, 0)}, {roundTo(state.color.rgb.g, 0)},{" "} + {roundTo(state.color.rgb.b, 0)}) +

+

HEX: #{state.color.hex.to_code()}

+
+ + ); +} + +describe("space editor tests", () => { + it("can edit color values", () => { + cy.mount(); + + // Confirm initial values + cy.dataCy("RGB-editor").within(() => { + cy.dataCy("R-value-input").should("have.value", 46); + cy.dataCy("G-value-input").should("have.value", 221); + cy.dataCy("B-value-input").should("have.value", 157); + }); + + cy.dataCy("HSV-editor").within(() => { + cy.dataCy("H-value-input").should("have.value", 158); + cy.dataCy("S-value-input").should("have.value", 79); + cy.dataCy("V-value-input").should("have.value", 87); + }); + + cy.dataCy("HCL-editor").within(() => { + cy.dataCy("H-value-input").should("have.value", 158); + cy.dataCy("C-value-input").should("have.value", 79); + cy.dataCy("L-value-input").should("have.value", 70); + }); + + cy.dataCy("rgb-value").should("have.text", "RGB (46, 221, 157)"); + cy.dataCy("hsv-value").should("have.text", "HSV (158, 0.79, 0.87)"); + cy.dataCy("hcl-value").should("have.text", "HCL (158, 0.79, 0.7)"); + cy.dataCy("hex-value").should("have.text", "HEX: #2EDD9D"); + + // Update the color values + cy.wait(50); // ensure render + cy.dataCy("HCL-editor").within(() => { + cy.dataCy("H-slider").click(); + cy.dataCy("C-decrement-button").click(); + cy.dataCy("L-value-input").type("25"); + }); + + cy.dataCy("rgb-value").should("have.text", "RGB (17, 76, 75)"); + cy.dataCy("hsv-value").should("have.text", "HSV (179, 0.78, 0.3)"); + cy.dataCy("hcl-value").should("have.text", "HCL (179, 0.78, 0.25)"); + cy.dataCy("hex-value").should("have.text", "HEX: #104B4A"); + }); +}); diff --git a/src/providers/SelectedColorProvider.tsx b/src/providers/SelectedColorProvider.tsx new file mode 100644 index 0000000..e0b4786 --- /dev/null +++ b/src/providers/SelectedColorProvider.tsx @@ -0,0 +1,39 @@ +import { createContext, useContext, useReducer } from "react"; +import type { ReactNode } from "react"; + +import * as colorlib from "colorlib"; + +import { colorReducer, createColorActions } from "@hooks/color"; +import type { ColorActions } from "@hooks/color"; + +interface SelectedColorContextType { + selectedColor: colorlib.Color; + selectedColorActions: ColorActions; +} + +export const SelectedColorContext = createContext< + SelectedColorContextType | undefined +>(undefined); + +export const SelectedColorProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const initialState = { + color: colorlib.Color.from_hex("00C9FA"), + }; + const [colorState, colorDispatch] = useReducer(colorReducer, initialState); + const colorActions = createColorActions(colorDispatch); + + const value = { + selectedColor: colorState.color, + selectedColorActions: colorActions, + }; + + return ( + + {children} + + ); +};