diff --git a/packages/pluggableWidgets/switch-native/package.json b/packages/pluggableWidgets/switch-native/package.json index 4923ddc7b..882c3df2d 100644 --- a/packages/pluggableWidgets/switch-native/package.json +++ b/packages/pluggableWidgets/switch-native/package.json @@ -1,7 +1,7 @@ { "name": "switch-native", "widgetName": "Switch", - "version": "1.1.0", + "version": "1.1.1", "license": "Apache-2.0", "repository": { "type": "git", @@ -24,6 +24,8 @@ }, "devDependencies": { "@mendix/pluggable-widgets-tools": "~10.0.1", + "@testing-library/jest-native": "^5.4.3", + "@testing-library/react-native": "^13.3.3", "eslint": "^7.32.0" } } diff --git a/packages/pluggableWidgets/switch-native/src/Switch.tsx b/packages/pluggableWidgets/switch-native/src/Switch.tsx index 9009521cf..d695141fd 100644 --- a/packages/pluggableWidgets/switch-native/src/Switch.tsx +++ b/packages/pluggableWidgets/switch-native/src/Switch.tsx @@ -1,5 +1,5 @@ import { flattenStyles } from "@mendix/piw-native-utils-internal"; -import { createElement, ReactElement, useCallback } from "react"; +import React, { createElement, ReactElement, useCallback, Fragment } from "react"; import { View, Text, Switch as SwitchComponent, Platform } from "react-native"; import { executeAction } from "@mendix/piw-utils-internal"; import { extractStyles } from "@mendix/pluggable-widgets-tools"; @@ -10,7 +10,7 @@ import { SwitchStyle, defaultSwitchStyle, CheckBoxInputType } from "./ui/Styles" export type Props = SwitchProps; export function Switch(props: Props): ReactElement { - const { label, labelOrientation, showLabel, name, onChange, booleanAttribute } = props; + const { label, labelOrientation, showLabel, name, onChange, booleanAttribute, labelPosition } = props; const combinedStyles = flattenStyles(defaultSwitchStyle, props.style); const styles = processStyles(combinedStyles); const horizontalOrientation = showLabel && labelOrientation === "horizontal"; @@ -39,36 +39,88 @@ export function Switch(props: Props): ReactElement { const labelValue = label?.status === "available" ? label.value : ""; + const switchElement = ( + + ); + + const labelElement = showLabel ? ( + + {labelValue} + + ) : null; + + const validationMessage = hasValidationMessage ? ( + + {booleanAttribute.validation} + + ) : null; + return ( - {showLabel ? ( - - {labelValue} - - ) : null} - - - {hasValidationMessage ? ( - - {booleanAttribute.validation} - - ) : null} - + {horizontalOrientation ? ( + // Horizontal layout: label and switch in a row, validation message below + <> + + {labelPosition === "right" ? ( + <> + {React.cloneElement(switchElement, { key: "switch" })} + + {labelElement && React.cloneElement(labelElement, { key: "label" })} + {validationMessage} + + + ) : ( + <> + + {labelElement && React.cloneElement(labelElement, { key: "label" })} + {validationMessage} + + {React.cloneElement(switchElement, { key: "switch" })} + + )} + + + ) : ( + // Vertical layout: label, switch, and validation message all in a column + <> + {labelElement && React.cloneElement(labelElement, { key: "label" })} + {React.cloneElement(switchElement, { key: "switch" })} + {validationMessage} + + )} ); } diff --git a/packages/pluggableWidgets/switch-native/src/Switch.xml b/packages/pluggableWidgets/switch-native/src/Switch.xml index 1bffec638..ae69d2e2f 100644 --- a/packages/pluggableWidgets/switch-native/src/Switch.xml +++ b/packages/pluggableWidgets/switch-native/src/Switch.xml @@ -1,7 +1,5 @@ - - + + Switch Toggle a boolean attribute. Display @@ -11,44 +9,52 @@ Attribute - + - + On change - + Show label - + Label - + Label orientation - + Horizontal Vertical + + Label position + The position of the label relative to the switch + + Left + Right + + - + - + - + diff --git a/packages/pluggableWidgets/switch-native/src/__tests__/Switch.spec.tsx b/packages/pluggableWidgets/switch-native/src/__tests__/Switch.spec.tsx index 4c38f64ec..cdedd6d0e 100644 --- a/packages/pluggableWidgets/switch-native/src/__tests__/Switch.spec.tsx +++ b/packages/pluggableWidgets/switch-native/src/__tests__/Switch.spec.tsx @@ -1,15 +1,9 @@ -/** - * @jest-environment jsdom - */ import { actionValue, EditableValueBuilder, dynamicValue } from "@mendix/piw-utils-internal"; -import { mount, ReactWrapper } from "enzyme"; +import { render, fireEvent, screen } from "@testing-library/react-native"; import { createElement } from "react"; - import { Switch, Props } from "../Switch"; import { defaultSwitchStyle } from "../ui/Styles"; -declare type RWrapper = ReactWrapper, React.Component<{}, {}, any>>; - const name = "Switch1"; const createProps = (props?: Partial): Props => { const style = props?.style ?? {}; @@ -20,36 +14,28 @@ const createProps = (props?: Partial): Props => { showLabel: false, booleanAttribute: new EditableValueBuilder().withValue(false).build(), onChange: undefined, - style: [{ ...defaultSwitchStyle, ...style }] + style: [{ ...defaultSwitchStyle, ...style }], + labelPosition: "left" }; return { ...defaultProps, ...props }; }; describe("Switch", () => { - const switchIndex = 0; let Platform: any; - let switchWrapper: RWrapper; - - function getSwitchComponent() { - return switchWrapper.find({ testID: "Switch1" }).at(switchIndex); - } beforeEach(() => { Platform = require("react-native").Platform; }); - afterEach(() => { - switchWrapper.unmount(); - }); - it("with editable value renders enabled", () => { const props = createProps({ booleanAttribute: new EditableValueBuilder().withValue(false).build() }); - switchWrapper = mount(); - expect(getSwitchComponent().prop("disabled")).toBe(false); + render(); + const switchElement = screen.getByTestId("Switch1"); + expect(switchElement.props.enabled).toBe(true); }); it("with value in readOnly state renders disabled", () => { @@ -57,8 +43,9 @@ describe("Switch", () => { booleanAttribute: new EditableValueBuilder().withValue(false).isReadOnly().build() }); - switchWrapper = mount(); - expect(getSwitchComponent().prop("disabled")).toBe(true); + render(); + const switchElement = screen.getByTestId("Switch1"); + expect(switchElement.props.enabled).toBe(false); }); it("with showLabel true renders label", () => { @@ -66,22 +53,27 @@ describe("Switch", () => { showLabel: true }); - switchWrapper = mount(); - expect(switchWrapper.exists({ testID: `${name}$label` })).toEqual(true); + render(); + expect(screen.getByTestId(`${name}$label`)).toBeTruthy(); }); - it("with showLabel true renders label horizontally", () => { + it("with showLabel true and horizontal orientation, renders label and switch in a row", () => { const props = createProps({ - showLabel: true + showLabel: true, + labelOrientation: "horizontal", + label: dynamicValue("Test Label", false) }); - switchWrapper = mount(); - expect( - switchWrapper - .find({ testID: `${name}$wrapper` }) - .at(1) - .prop("style") - ).toEqual(expect.arrayContaining([{ flexDirection: "row", alignItems: "center" }])); + render(); + + const horizontalContainer = screen.getByTestId(`${name}$horizontalContainer`); + + expect(horizontalContainer.props.style).toEqual( + expect.objectContaining({ flexDirection: "row", alignItems: "center" }) + ); + + expect(horizontalContainer).toContainElement(screen.getByTestId(`${name}$label`)); + expect(horizontalContainer).toContainElement(screen.getByTestId(name)); }); it("with showLabel true and labelOrientation vertical, renders vertical", () => { @@ -90,14 +82,12 @@ describe("Switch", () => { labelOrientation: "vertical" }); - switchWrapper = mount(); - - expect( - switchWrapper - .find({ testID: `${name}$wrapper` }) - .at(1) - .prop("style") - ).toEqual(expect.not.arrayContaining([{ flexDirection: "row", alignItems: "center" }])); + render(); + const wrapper = screen.getByTestId(`${name}$wrapper`); + expect(wrapper.props.style).toEqual( + expect.arrayContaining([{ flexDirection: "column", alignItems: "flex-start" }]) + ); + expect(screen.queryByTestId(`${name}$horizontalContainer`)).toBeNull(); }); it("with error renders validation message", () => { @@ -105,32 +95,18 @@ describe("Switch", () => { booleanAttribute: new EditableValueBuilder().withValidation("error").withValue(false).build() }); - switchWrapper = mount(); - expect(switchWrapper.prop("booleanAttribute").validation).toEqual("error"); - - expect(switchWrapper.exists({ testID: `${name}$alert` })).toEqual(true); - expect( - switchWrapper - .find({ testID: `${name}$alert` }) - .at(1) - .text() - ).toEqual("error"); - }); - - it("with iOS device renders correct property", () => { - Platform.OS = "ios"; - const props = createProps(); - - switchWrapper = mount(); - expect(getSwitchComponent().props()).toEqual(expect.objectContaining({ ios_backgroundColor: undefined })); + render(); + expect(props.booleanAttribute.validation).toEqual("error"); + expect(screen.getByTestId(`${name}$alert`)).toBeTruthy(); + expect(screen.getByTestId(`${name}$alert`).props.children).toEqual("error"); }); it("with android device renders property", () => { Platform.OS = "android"; const props = createProps(); - switchWrapper = mount(); - expect(getSwitchComponent().prop("ios_backgroundColor")).toBeUndefined(); + render(); + expect(screen.getByTestId("Switch1").props.ios_backgroundColor).toBeUndefined(); }); it("renders correct thumbColor when value is true", () => { @@ -139,8 +115,8 @@ describe("Switch", () => { style: [{ ...defaultSwitchStyle, input: { thumbColorOn: "red" } }] }); - switchWrapper = mount(); - expect(getSwitchComponent().prop("thumbColor")).toEqual("red"); + render(); + expect(screen.getByTestId("Switch1").props.thumbTintColor).toEqual("red"); }); it("renders correct thumbColor when value is false", () => { @@ -149,41 +125,45 @@ describe("Switch", () => { style: [{ ...defaultSwitchStyle, input: { thumbColorOff: "blue" } }] }); - switchWrapper = mount(); - expect(getSwitchComponent().prop("thumbColor")).toEqual("blue"); + render(); + expect(screen.getByTestId("Switch1").props.thumbTintColor).toEqual("blue"); }); describe("interactions", () => { it("invokes onValueChange handler", () => { + const onChange = actionValue(); + const booleanAttribute = new EditableValueBuilder().withValue(false).build(); const props = createProps({ - booleanAttribute: new EditableValueBuilder().withValue(false).build(), - onChange: actionValue() + booleanAttribute, + onChange }); - switchWrapper = mount(); + render(); - expect(switchWrapper.prop("booleanAttribute").value).toBe(false); - expect(switchWrapper.prop("onChange").execute).not.toHaveBeenCalled(); + expect(booleanAttribute.value).toBe(false); + expect(onChange.execute).not.toHaveBeenCalled(); - getSwitchComponent().simulate("change"); + fireEvent(screen.getByTestId("Switch1"), "valueChange", true); - expect(switchWrapper.prop("booleanAttribute").value).toBe(true); - expect(switchWrapper.prop("onChange").execute).toHaveBeenCalled(); + expect(booleanAttribute.value).toBe(true); + expect(onChange.execute).toHaveBeenCalled(); }); it("when disabled, do not invoke onValueChange handler", () => { + const onChange = actionValue(); + const booleanAttribute = new EditableValueBuilder().withValue(false).isReadOnly().build(); const props = createProps({ - booleanAttribute: new EditableValueBuilder().withValue(false).isReadOnly().build(), - onChange: actionValue() + booleanAttribute, + onChange }); - switchWrapper = mount(); + render(); - expect(switchWrapper.prop("booleanAttribute").value).toBe(false); - expect(switchWrapper.prop("onChange").execute).not.toHaveBeenCalled(); + expect(booleanAttribute.value).toBe(false); + expect(onChange.execute).not.toHaveBeenCalled(); - getSwitchComponent().simulate("change"); + fireEvent(screen.getByTestId("Switch1"), "valueChange", true); - expect(switchWrapper.prop("booleanAttribute").value).toBe(false); - expect(switchWrapper.prop("onChange").execute).not.toHaveBeenCalled(); + expect(booleanAttribute.value).toBe(false); + expect(onChange.execute).not.toHaveBeenCalled(); }); }); -}); +}); \ No newline at end of file diff --git a/packages/pluggableWidgets/switch-native/src/assets/checked-dark.svg b/packages/pluggableWidgets/switch-native/src/assets/checked-dark.svg index bd5e31bb6..d80ba755e 100644 --- a/packages/pluggableWidgets/switch-native/src/assets/checked-dark.svg +++ b/packages/pluggableWidgets/switch-native/src/assets/checked-dark.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/pluggableWidgets/switch-native/src/assets/checked.svg b/packages/pluggableWidgets/switch-native/src/assets/checked.svg index 15afc0cea..6a828d278 100644 --- a/packages/pluggableWidgets/switch-native/src/assets/checked.svg +++ b/packages/pluggableWidgets/switch-native/src/assets/checked.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/pluggableWidgets/switch-native/src/package.xml b/packages/pluggableWidgets/switch-native/src/package.xml index b7e7c6f86..36e64d933 100644 --- a/packages/pluggableWidgets/switch-native/src/package.xml +++ b/packages/pluggableWidgets/switch-native/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/switch-native/src/ui/Styles.ts b/packages/pluggableWidgets/switch-native/src/ui/Styles.ts index f97731217..c4af5569e 100644 --- a/packages/pluggableWidgets/switch-native/src/ui/Styles.ts +++ b/packages/pluggableWidgets/switch-native/src/ui/Styles.ts @@ -25,7 +25,6 @@ export interface SwitchStyle extends Style { export const defaultSwitchStyle: SwitchStyle = { container: { // All ViewStyle properties are allowed - paddingVertical: 4, justifyContent: "center" }, containerDisabled: { @@ -50,6 +49,5 @@ export const defaultSwitchStyle: SwitchStyle = { }, validationMessage: { // All TextStyle properties are allowed - alignSelf: "stretch" } }; diff --git a/packages/pluggableWidgets/switch-native/tsconfig.json b/packages/pluggableWidgets/switch-native/tsconfig.json index 89776d5ca..5b6279c21 100644 --- a/packages/pluggableWidgets/switch-native/tsconfig.json +++ b/packages/pluggableWidgets/switch-native/tsconfig.json @@ -3,6 +3,7 @@ "baseUrl": "./", "include": ["./src", "./typings"], "compilerOptions": { - "typeRoots": ["node_modules/@types", "../../../node_modules/@types"] + "typeRoots": ["node_modules/@types", "../../../node_modules/@types"], + "jsxFragmentFactory": "Fragment" } } diff --git a/packages/pluggableWidgets/switch-native/typings/SwitchProps.d.ts b/packages/pluggableWidgets/switch-native/typings/SwitchProps.d.ts index 45403a3de..c9da75030 100644 --- a/packages/pluggableWidgets/switch-native/typings/SwitchProps.d.ts +++ b/packages/pluggableWidgets/switch-native/typings/SwitchProps.d.ts @@ -8,6 +8,8 @@ import { ActionValue, DynamicValue, EditableValue } from "mendix"; export type LabelOrientationEnum = "horizontal" | "vertical"; +export type LabelPositionEnum = "left" | "right"; + export interface SwitchProps