From c5cf5f6503a21cc3b242b73155b354486e29b096 Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Mon, 6 Mar 2023 16:00:53 -0700 Subject: [PATCH] [F] finish and add tests --- .../widgets/GalaxySelector/GalaxySelector.tsx | 2 + .../GalaxySelector/Point/Point.stories.tsx | 20 +- .../GalaxySelector/Point/Point.test.tsx | 27 +++ .../widgets/GalaxySelector/Point/Point.tsx | 2 + .../widgets/GalaxySelector/Point/styles.ts | 7 +- .../GalaxySelector/Points/Points.stories.tsx | 190 ++++++++++++++++++ .../GalaxySelector/Points/Points.test.tsx | 64 ++++++ .../widgets/GalaxySelector/Points/Points.tsx | 46 +++-- .../widgets/GalaxySelector/utilities.test.ts | 22 ++ 9 files changed, 349 insertions(+), 31 deletions(-) create mode 100644 packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.test.tsx create mode 100644 packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.test.tsx create mode 100644 packages/epo-widget-lib/src/widgets/GalaxySelector/utilities.test.ts diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/GalaxySelector.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/GalaxySelector.tsx index c256f4e5..6f5b06a0 100644 --- a/packages/epo-widget-lib/src/widgets/GalaxySelector/GalaxySelector.tsx +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/GalaxySelector.tsx @@ -12,6 +12,8 @@ export interface AstroDataset { export type AstroType = "supernova" | "galaxy" | "galaxyFilter"; +export type AccessorKey = "ra" | "dec"; + export interface AstroObject { object_id: string; id: AstroType; diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.stories.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.stories.tsx index 6572b2d1..4f5d9aba 100644 --- a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.stories.tsx +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.stories.tsx @@ -12,9 +12,16 @@ const meta: ComponentMeta = { type: { summary: "supernova | galaxy | galaxyFilter", }, + category: "Display", + }, + }, + className: { + ...className, + table: { + ...className.table, + category: "Styling", }, }, - className, isSelected: { control: "boolean", description: "Determines if the point has been selected and is visible", @@ -22,6 +29,7 @@ const meta: ComponentMeta = { type: { summary: "boolean", }, + category: "Display", }, }, isActive: { @@ -31,6 +39,7 @@ const meta: ComponentMeta = { type: { summary: "boolean", }, + category: "Display", }, }, x: { @@ -40,6 +49,7 @@ const meta: ComponentMeta = { type: { summary: "number", }, + category: "Display", }, }, y: { @@ -49,6 +59,7 @@ const meta: ComponentMeta = { type: { summary: "number", }, + category: "Display", }, }, radius: { @@ -59,6 +70,7 @@ const meta: ComponentMeta = { type: { summary: "number", }, + category: "Display", }, }, color: { @@ -68,6 +80,7 @@ const meta: ComponentMeta = { type: { summary: "string", }, + category: "Styling", }, }, }, @@ -78,9 +91,8 @@ const meta: ComponentMeta = { viewBox="0 0 100 100" style={{ display: "inline-block", - width: "auto", - height: "100%", - maxHeight: "200px", + width: "100%", + height: "auto", }} > diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.test.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.test.tsx new file mode 100644 index 00000000..9a8f1f38 --- /dev/null +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.test.tsx @@ -0,0 +1,27 @@ +import { render, screen } from "@testing-library/react"; +import Point from "."; + +const props = { + x: 50, + y: 50, + radius: 10, + isSelected: false, + isActive: false, + color: "#000", +}; + +describe("Point", () => { + it(`should populate cx, cy, and r attributes`, () => { + render( + + + + ); + + const point = screen.getByRole("listitem"); + + expect(point).toHaveAttribute("cx", props.x.toString()); + expect(point).toHaveAttribute("cy", props.y.toString()); + expect(point).toHaveAttribute("r", props.radius.toString()); + }); +}); diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.tsx index b8c41546..4a1e7884 100644 --- a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.tsx +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/Point.tsx @@ -61,6 +61,8 @@ const Point: FunctionComponent = ({ r={isActive ? activeRadius : baseRadius} fill="transparent" stroke={getStroke(isActive, isSelected, color)} + tabIndex={0} + role="listitem" {...{ isSelected, className }} /> ); diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/styles.ts b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/styles.ts index 8811281b..b3fcc4ee 100644 --- a/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/styles.ts +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Point/styles.ts @@ -1,7 +1,4 @@ -import styled, { css, keyframes } from "styled-components"; - -const growRadius = (r: number) => - keyframes`from {r: ${r}} to {r: ${Math.max(10, r * 1.2)}}`; +import styled, { css } from "styled-components"; export const Point = styled.circle<{ isSelected: boolean; @@ -11,7 +8,7 @@ export const Point = styled.circle<{ ${({ isSelected }) => isSelected && css` - stroke-width: 4; + stroke-width: 3; `} &:focus { diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.stories.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.stories.tsx index e69de29b..3b68de14 100644 --- a/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.stories.tsx +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.stories.tsx @@ -0,0 +1,190 @@ +import { className } from "@/storybook/utilities/argTypes"; +import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; + +import Points from "."; +import { AstroDataset } from "../GalaxySelector"; +import { getLinearScale } from "../utilities"; + +const width = 600; +const height = 600; +const domain = [0, 1200]; +const data: AstroDataset = { + name: "All Three Filters", + id: "ZTF19abvhduf", + color: "#fed828", + distance: 124.63102831794073, + dec: 56.07525, + velocity: 8573.901530035577, + ra: 97.139708, + redshift: 0.029, + objects: [ + { + object_id: "01", + id: "galaxy", + ra: 120, + dec: 780, + color: "#fed828", + }, + { + object_id: "02", + id: "galaxyFilter", + ra: 590, + dec: 630, + color: "#fed828", + }, + { + object_id: "03", + id: "supernova", + ra: 990, + dec: 440, + color: "#fed828", + }, + ], +}; + +const meta: ComponentMeta = { + argTypes: { + className: { + ...className, + table: { + ...className.table, + category: "Styling", + }, + }, + data: { + control: "object", + description: "Array of objects to be rendered as points.", + table: { + type: { + summary: "AstroObject[]", + }, + defaultValue: { + summary: "[]", + }, + category: "Model", + }, + }, + activeId: { + control: "select", + options: ["supernova", "galaxy", "galaxyFilter", "none"], + description: + "ID of the currently active point that will be given active styling", + table: { + type: { + summary: "supernova | galaxy | galaxyFilter", + }, + category: "Model", + }, + }, + selectedData: { + control: "object", + description: + "ID of the currently selected point that will be visible to the user.", + table: { + type: { + summary: "AstroObject[]", + }, + category: "Model", + }, + }, + xValueAccessor: { + type: { + name: "string", + required: true, + }, + control: "select", + options: ["ra", "dec"], + description: "Key to access the x value of data points.", + table: { + type: { + summary: "ra | dec", + }, + category: "Placement", + }, + }, + yValueAccessor: { + type: { + name: "string", + required: true, + }, + control: "select", + options: ["ra", "dec"], + description: "Key to access the y value of data points.", + table: { + type: { + summary: "ra | dec", + }, + category: "Placement", + }, + }, + xScale: { + type: { + name: "function", + required: true, + }, + description: + "Scaling function to place points on the appropriate x value given the data range.", + table: { + type: { + summary: "(value: number) => number", + }, + category: "Placement", + }, + }, + yScale: { + type: { + name: "function", + required: true, + }, + description: + "Scaling function to place points on the appropriate y value given the data range.", + table: { + type: { + summary: "(value: number) => number", + }, + category: "Placement", + }, + }, + color: { + control: "color", + description: + "Override color to be used in place of data points defined colors.", + table: { + type: { + summary: "string", + }, + category: "Styling", + }, + }, + }, + component: Points, + decorators: [ + (Story) => ( + + + + ), + ], +}; +export default meta; + +const xScale = getLinearScale(domain, [0, width]); +const yScale = getLinearScale(domain, [height, 0]); + +export const Primary: ComponentStoryObj = { + args: { + data: data.objects, + selectedData: [data.objects[0]], + xScale, + yScale, + xValueAccessor: "ra", + yValueAccessor: "dec", + }, +}; diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.test.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.test.tsx new file mode 100644 index 00000000..7895f4ee --- /dev/null +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.test.tsx @@ -0,0 +1,64 @@ +import { render, screen } from "@testing-library/react"; +import Points from "."; +import { AstroDataset } from "../GalaxySelector"; +import { getLinearScale } from "../utilities"; + +const width = 600; +const height = 600; +const domain = [0, 1200]; +const data: AstroDataset = { + name: "All Three Filters", + id: "ZTF19abvhduf", + color: "#fed828", + distance: 124.63102831794073, + dec: 56.07525, + velocity: 8573.901530035577, + ra: 97.139708, + redshift: 0.029, + objects: [ + { + object_id: "01", + id: "galaxy", + ra: 120, + dec: 780, + color: "#fed828", + }, + { + object_id: "02", + id: "galaxyFilter", + ra: 590, + dec: 630, + color: "#fed828", + }, + { + object_id: "03", + id: "supernova", + ra: 990, + dec: 440, + color: "#fed828", + }, + ], +}; + +const xScale = getLinearScale(domain, [0, width]); +const yScale = getLinearScale(domain, [height, 0]); + +const props = { + data: data.objects, + xScale, + yScale, +}; + +describe("Points", () => { + it(`should render a point for each object in the dataset`, () => { + render( + + + + ); + + const points = screen.getAllByRole("listitem"); + + expect(points.length).toBe(props.data.length); + }); +}); diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.tsx b/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.tsx index fabff5b8..0df5656c 100644 --- a/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.tsx +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/Points/Points.tsx @@ -1,40 +1,43 @@ import { FunctionComponent } from "react"; -import { AstroDataset, AstroObject } from "../GalaxySelector"; +import { AccessorKey, AstroObject, AstroType } from "../GalaxySelector"; import Point from "../Point"; interface PointsProps { - data: AstroObject[]; - selectedData: AstroObject; - active: AstroDataset; - xValueAccessor: string; - yValueAccessor: string; + data?: AstroObject[]; + selectedData?: AstroObject[]; + activeId?: AstroType; + xValueAccessor: AccessorKey; + yValueAccessor: AccessorKey; xScale: (value: number) => number; yScale: (value: number) => number; - className: string; - color: string; + className?: string; + color?: string; } const Points: FunctionComponent = ({ - data, - selectedData, - active, + data = [], + selectedData = [], + activeId, className, xScale, yScale, xValueAccessor, yValueAccessor, + color: colorOverride, }) => { return ( - + {data.map((d) => { - const { id, color, radius = NaN, [xValueAccessor]: xVal } = d; + const { + id, + color, + radius = NaN, + [xValueAccessor]: xVal, + [yValueAccessor]: yVal, + } = d; const modR = 0.6 * radius; - const isSelected = !!find(selectedData, { id: d.id }); - const isActive = active ? active.id === d.id : false; - const pointColor = isNumber(color) ? chartColors.chart6 : color; - const colorOverrideHex = colorOverride - ? chartColors[`chart${colorOverride}`] - : false; + const isSelected = selectedData.some((s) => s.id === id); + const isActive = activeId ? activeId === id : false; return ( = ({ {...{ id, isActive, isSelected, className }} radius={xScale(xVal - modR) - xScale(xVal + modR)} x={xScale(xVal)} - y={yScale(d[yValueAccessor])} - color={colorOverrideHex || pointColor} - tabIndex="0" + y={yScale(yVal)} + color={colorOverride || color} /> ); })} diff --git a/packages/epo-widget-lib/src/widgets/GalaxySelector/utilities.test.ts b/packages/epo-widget-lib/src/widgets/GalaxySelector/utilities.test.ts new file mode 100644 index 00000000..898f6c19 --- /dev/null +++ b/packages/epo-widget-lib/src/widgets/GalaxySelector/utilities.test.ts @@ -0,0 +1,22 @@ +import { getLinearScale } from "./utilities"; + +const domain = [0, 10]; +const range = [100, 200]; + +describe("getLinearScale", () => { + it("should return a method", () => { + const scale = getLinearScale(domain, range); + + expect(typeof scale).toBe("function"); + }); + it("should scale values in domain to new range", () => { + const scale = getLinearScale(domain, range); + const minOutput = scale(domain[0]); + const maxOutput = scale(domain[1]); + const output = scale(5); + + expect(minOutput).toBe(range[0]); + expect(maxOutput).toBe(range[1]); + expect(output).toBe(150); + }); +});