Skip to content

Commit

Permalink
feat: Update plugins menu configuration (#1410)
Browse files Browse the repository at this point in the history
* feat: plugins menu config hosted locally; support morePlugins url param
* chore: update cypress tests
* chore: support absolute urls in addition to relative urls
* chore: self code review tweaks
  • Loading branch information
kswenson authored Aug 19, 2024
1 parent 5f1d98e commit 25de329
Show file tree
Hide file tree
Showing 22 changed files with 85 additions and 129 deletions.
2 changes: 0 additions & 2 deletions v3/cypress/e2e/adornments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import { ToolbarElements as toolbar } from "../support/elements/toolbar-elements

context("Graph adornments", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})

it("shows inspector palette when Display Values button is clicked", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/attribute-types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { CfmElements as cfm } from "../support/elements/cfm"

context("attribute types", () => {
beforeEach(() => {
cy.log('Starting test setup')
const filename = "cypress/fixtures/attribute-types.codap"
const url = `${Cypress.config("index")}`
cy.visit(url)
cy.wait(3000)
cfm.openLocalDoc(filename)
cy.log('Setup complete')
})

describe("attribute types are rendered correctly", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/bivariate-adornments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { ToolbarElements as toolbar } from "../support/elements/toolbar-elements

context("Graph adornments", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})

it("adds a least squares line to graph when Least Squares Line checkbox is checked", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/calculator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ const calculatorName = "Calculator"

context("Calculator", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500) // Ensuring the page and components are fully loaded.
cy.log('Setup complete')
})
it("populates default title", () => {
c.getComponentTitle("calculator").should("contain", calculatorName)
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/case-card.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@

beforeEach(() => {
// cy.scrollTo() doesn't work as expected with `scroll-behavior: smooth`
cy.log('Starting test setup')
const queryParams = "?sample=mammals&scrollBehavior=auto"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2000)
cy.log('Setup complete')
})

context("case card", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/cfm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { TableTileElements as table } from "../support/elements/table-tile"

context("CloudFileManager", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})
it("Opens Mammals example document via CFM Open dialog", () => {
// hamburger menu is hidden initially
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { ComponentElements as c } from "../support/elements/component-elements"

context("Component UI", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})

it("moves components by dragging", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/graph-legend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ const arrayOfValues = [

context("Test legend with various attribute types", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})
it("will not draw legend if plot area is empty", () => {
glh.dragAttributeToPlot(arrayOfAttributes[7]) // Habitat => plot area
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ const plots = graphRules.plots
// (In local, this works fine and the tests can be run successfully)
context.skip("Test graph plot transitions", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cfm.openLocalDoc("cypress/fixtures/3TableGroups.codap")
cy.wait(2500)
cy.log('Setup complete')
})

plots.forEach(test => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/hierarchical-table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ const values = hierarchical.attributes

context("hierarchical collections", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})
hierarchical.tests.forEach((h: HierarchicalTest) => {
// FIXME: enable skipped tests
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/map.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ const arrayOfAttributes = ["Category", "Educ_Tertiary_Perc", "Inversions"]

context("Map UI", () => {
beforeEach(function () {
cy.log('Starting test setup')
const url = `${Cypress.config("index")}?mouseSensor&noComponentAnimation`
cy.visit(url)
cy.wait(3000)
cy.log('Setup complete')
})

it("verify map title", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import { WebViewTileElements as webView } from "../support/elements/web-view-til

context("codap plugins", () => {
beforeEach(function () {
cy.log('Starting test setup')
const url = `${Cypress.config("index")}?sample=mammals&dashboard`
cy.visit(url)
cy.log('Setup complete')
})
const openAPITester = () => {
const url='https://concord-consortium.github.io/codap-data-interactives/DataInteractiveAPITester/index.html?lang=en'
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/slider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ const newSliderValue = "0.6"

context("Slider UI", () => {
beforeEach(function () {
cy.log('Starting test setup')
const queryParams = "?sample=mammals&dashboard&mouseSensor"
const url = `${Cypress.config("index")}${queryParams}`
cy.visit(url)
cy.wait(2500)
cy.log('Setup complete')
})
it("populates default title, variable name and value", () => {
c.getComponentTitle("slider").should("contain", sliderName)
Expand Down
5 changes: 0 additions & 5 deletions v3/cypress/e2e/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,8 @@ const newCollectionName = "New Dataset"

beforeEach(() => {
// cy.scrollTo() doesn't work as expected with `scroll-behavior: smooth`
cy.log('Starting test setup')
const queryParams = "?sample=mammals&scrollBehavior=auto"
const url = `${Cypress.config("index")}${queryParams}`
cy.intercept("GET", "https://codap-resources.s3.amazonaws.com/plugins/published-plugins.json", {
fixture: "mockPublishedPlugins.json"
})
cy.visit(url)
cy.wait(1000)
table.getNumOfAttributes().should("equal", numOfAttributes.toString())
Expand All @@ -28,7 +24,6 @@ beforeEach(() => {
lastRowIndex = Number($cases) - 1
middleRowIndex = Math.min(5, Math.floor(lastRowIndex / 2))
})
cy.log('Setup complete')
})

context("case table ui", () => {
Expand Down
2 changes: 0 additions & 2 deletions v3/cypress/e2e/tool-shelf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import { WebViewTileElements as webView } from "../support/elements/web-view-til

context("codap toolbar", () => {
beforeEach(function () {
cy.log('Starting test setup')
const url = `${Cypress.config("index")}?mouseSensor`
cy.visit(url)
cy.log('Setup complete')
})
it("will open a new table", () => {
c.clickIconFromToolShelf("table")
Expand Down
16 changes: 16 additions & 0 deletions v3/src/components/tool-shelf/plugin-config-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface PluginData {
aegis?: string,
categories: string[],
description: string,
"description-string"?: string,
height: number,
icon: string,
isStandard: "true" | "false", // All have "true" for some reason
path: string,
title: string,
"title-string"?: string,
visible: boolean | "true" | "false", // Most have "true" or "false" for some reason, but a couple have true
width: number
}

export type PluginMenuConfig = PluginData[]
38 changes: 2 additions & 36 deletions v3/src/components/tool-shelf/plugins-button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,15 @@ import { render, screen } from "@testing-library/react"
import { userEvent } from "@testing-library/user-event"
import React from "react"
import { PluginsButton } from "./plugins-button"
import { t } from "../../utilities/translation/translate"
import { PluginData } from "../../hooks/use-standard-plugins"

describe("PluginsButtons", () => {
const user = userEvent.setup()

it("renders with no plugins", async () => {
// fetch returns empty plugins array
fetchMock.mockResponseOnce("[]")
it("renders with standard plugins", async () => {
render(<PluginsButton/>)
expect(await screen.findByTestId("tool-shelf-button-plugins")).toBeInTheDocument()
// click the button
user.click(screen.getByTestId("tool-shelf-button-plugins"))
expect(await screen.findByText(t("V3.ToolButtonData.pluginMenu.fetchError"))).toBeInTheDocument()
})

it("renders with a plugin", async () => {
// fetch returns an array with a single plugin
const plugins: PluginData[] = [{
categories: [],
description: "",
height: 100,
icon: "",
isStandard: "true",
path: "",
title: "Test Plugin",
visible: "true",
width: 100
}]
fetchMock.mockResponseOnce(JSON.stringify(plugins))
const errorSpy = jest.spyOn(console, "error").mockImplementation(() => null)
render(<PluginsButton/>)
expect(await screen.findByTestId("tool-shelf-button-plugins")).toBeInTheDocument()
// click the button
user.click(screen.getByTestId("tool-shelf-button-plugins"))
expect(await screen.findByText("Test Plugin")).toBeInTheDocument()
// I spent a while trying to find a better solution to this problem without success.
// The problem is complicated by the fact that the setState call at issue comes from
// Chakra's menu component rather than our own components. In the end, we just
// suppress the error and assert that it's the only error that occurred.
expect(errorSpy).toHaveBeenCalledTimes(1)
const expectedErrorStr = "Warning: An update to %s inside a test was not wrapped in act(...)"
expect(errorSpy.mock.calls[0][0].startsWith(expectedErrorStr)).toBe(true)
errorSpy.mockRestore()
expect(await screen.findByText("Sampler")).toBeInTheDocument()
})
})
31 changes: 19 additions & 12 deletions v3/src/components/tool-shelf/plugins-button.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
import React from "react"
import { Menu, MenuButton, MenuItem, MenuList } from "@chakra-ui/react"
import { Menu, MenuButton, MenuDivider, MenuItem, MenuList } from "@chakra-ui/react"
import PluginsIcon from '../../assets/icons/icon-plug.svg'
import { PluginData, useStandardPlugins } from "../../hooks/use-standard-plugins"
import { useRemotePluginsConfig } from "../../hooks/use-remote-plugins-config"
import { useDocumentContent } from "../../hooks/use-document-content"
import { t } from "../../utilities/translation/translate"
import { kWebViewTileType } from "../web-view/web-view-defs"
import { IWebViewModel } from "../web-view/web-view-model"
import { isWebViewModel } from "../web-view/web-view-model"
import { kRootPluginUrl, processPluginUrl } from "../web-view/web-view-utils"
import { ToolShelfButtonTag } from "./tool-shelf-button"
import { PluginData, PluginMenuConfig } from "./plugin-config-types"
import _standardPlugins from "./standard-plugins.json"
const standardPlugins = _standardPlugins as PluginMenuConfig

import "./plugins-button.scss"

interface IPluginItemProps {
pluginData: PluginData
pluginData: PluginData | null
}
function PluginItem({ pluginData }: IPluginItemProps) {
const documentContent = useDocumentContent()

function handleClick() {
if (!pluginData) return
documentContent?.applyModelChange(
() => {
const baseUrl = `${kRootPluginUrl}${pluginData.path}`
const url = processPluginUrl(baseUrl)
const url = URL.canParse(pluginData.path)
? pluginData.path
: processPluginUrl(`${kRootPluginUrl}${pluginData.path}`)
const options = { height: pluginData.height, width: pluginData.width }
const tile = documentContent?.createOrShowTile?.(kWebViewTileType, options)
if (tile) (tile.content as IWebViewModel).setUrl(url)
if (isWebViewModel(tile?.content)) tile.content.setUrl(url)
}, {
undoStringKey: t("V3.Undo.plugin.create", { vars: [pluginData.title] }),
redoStringKey: t("V3.Redo.plugin.create", { vars: [pluginData.title] })
}
)
}

return (
return pluginData ? (
<MenuItem
data-testid="tool-shelf-plugins-option"
onClick={handleClick}
Expand All @@ -42,11 +47,13 @@ function PluginItem({ pluginData }: IPluginItemProps) {
<span className="plugin-selection-title">{pluginData.title}</span>
</div>
</MenuItem>
)
) : <MenuDivider/>
}

export function PluginsButton() {
const { plugins } = useStandardPlugins()
const { plugins: remotePlugins } = useRemotePluginsConfig()
const pluginItems: Array<PluginData | null> =
remotePlugins.length ? [...standardPlugins, null, ...remotePlugins] : standardPlugins

return (
<Menu isLazy>
Expand All @@ -60,8 +67,8 @@ export function PluginsButton() {
</MenuButton>
<MenuList>
{
plugins.length
? plugins.map(pd => <PluginItem key={pd.title} pluginData={pd} />)
pluginItems.length
? pluginItems.map((pd, i) => <PluginItem key={pd?.title ?? `divider-${i}`} pluginData={pd} />)
: <MenuItem>{t("V3.ToolButtonData.pluginMenu.fetchError")}</MenuItem>
}
</MenuList>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { renderHook, waitFor } from "@testing-library/react"
import { useStandardPlugins } from "./use-standard-plugins"
import { useRemotePluginsConfig } from "./use-remote-plugins-config"

// mock urlParams to have a morePlugins parameter
// url doesn't matter since response is mocked below
jest.mock("../utilities/url-params", () => ({
urlParams: {
morePlugins: "https://codap-resources.s3.amazonaws.com/plugins/published-plugins.json"
}
}))

describe("useStandardPlugins", () => {

it("handles fetch throwing an error", async () => {
fetchMock.mockRejectOnce(new Error())
const spy = jest.spyOn(console, "warn").mockImplementation(() => null)
const { result } = renderHook(() => useStandardPlugins())
const { result } = renderHook(() => useRemotePluginsConfig())
await waitFor(() => {
expect(result.current.status).not.toBe("pending")
})
Expand All @@ -19,7 +27,7 @@ describe("useStandardPlugins", () => {
// !ok response from fetch
fetchMock.mockResponseOnce("[]", { status: 500 })
const spy = jest.spyOn(console, "warn").mockImplementation(() => null)
const { result } = renderHook(() => useStandardPlugins())
const { result } = renderHook(() => useRemotePluginsConfig())
await waitFor(() => {
expect(result.current.status).not.toBe("pending")
})
Expand All @@ -30,7 +38,7 @@ describe("useStandardPlugins", () => {

it("handles fetch returning empty plugins array", async () => {
fetchMock.mockResponseOnce("[]")
const { result } = renderHook(() => useStandardPlugins())
const { result } = renderHook(() => useRemotePluginsConfig())
await waitFor(() => {
expect(result.current.status).not.toBe("pending")
})
Expand Down
Loading

0 comments on commit 25de329

Please sign in to comment.