diff --git a/pkg/app/web/.scaffdog/module.md b/pkg/app/web/.scaffdog/module.md index a75a8572d8..efd4668068 100644 --- a/pkg/app/web/.scaffdog/module.md +++ b/pkg/app/web/.scaffdog/module.md @@ -29,7 +29,7 @@ export const {{ input | camel }}Slice = createSlice({ import { {{ input | camel }}Slice } from "./{{ input }}"; describe("{{ input | camel }}Slice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( {{ input | camel }}Slice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/__fixtures__/dummy-application.ts b/pkg/app/web/src/__fixtures__/dummy-application.ts index c58cea3499..2745723370 100644 --- a/pkg/app/web/src/__fixtures__/dummy-application.ts +++ b/pkg/app/web/src/__fixtures__/dummy-application.ts @@ -1,10 +1,22 @@ import { ApplicationKind } from "pipe/pkg/app/web/model/common_pb"; -import { ApplicationSyncStatus } from "../modules/applications"; +import { + Application, + ApplicationSyncStatus, + ApplicationSyncState, +} from "../modules/applications"; import { dummyEnv } from "./dummy-environment"; import { dummyPiped } from "./dummy-piped"; import { dummyRepo } from "./dummy-repo"; -export const dummyApplication = { +export const dummyApplicationSyncState: ApplicationSyncState = { + headDeploymentId: "deployment-1", + reason: "", + shortReason: "", + status: ApplicationSyncStatus.SYNCED, + timestamp: 0, +}; + +export const dummyApplication: Application = { id: "application-1", cloudProvider: "kubernetes-default", createdAt: 0, @@ -35,12 +47,7 @@ export const dummyApplication = { startedAt: 0, version: "v1", }, - syncState: { - headDeploymentId: "deployment-1", - reason: "", - shortReason: "", - status: ApplicationSyncStatus.SYNCED, - timestamp: 0, - }, + syncState: dummyApplicationSyncState, updatedAt: 0, + deleted: false, }; diff --git a/pkg/app/web/src/components/diff-view.test.tsx b/pkg/app/web/src/components/diff-view.test.tsx new file mode 100644 index 0000000000..0887ea9c8e --- /dev/null +++ b/pkg/app/web/src/components/diff-view.test.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { render, screen } from "../../test-utils"; +import { DiffView } from "./diff-view"; + +it("should render normal text", () => { + render(, {}); + + expect(screen.queryByTestId("added-line")).not.toBeInTheDocument(); + expect(screen.queryByTestId("deleted-line")).not.toBeInTheDocument(); +}); + +it("should render line as added line if the line start with'+'", () => { + render(, {}); + + expect(screen.getByTestId("added-line")).toBeInTheDocument(); + expect(screen.queryByTestId("deleted-line")).not.toBeInTheDocument(); +}); + +it("should render line as deleted line if the line start with '-'", () => { + render(, {}); + + expect(screen.queryByTestId("added-line")).not.toBeInTheDocument(); + expect(screen.getByTestId("deleted-line")).toBeInTheDocument(); +}); diff --git a/pkg/app/web/src/components/diff-view.tsx b/pkg/app/web/src/components/diff-view.tsx index 480d0be5dc..45c75a287e 100644 --- a/pkg/app/web/src/components/diff-view.tsx +++ b/pkg/app/web/src/components/diff-view.tsx @@ -1,5 +1,7 @@ -import React, { FC } from "react"; +import React, { FC, memo } from "react"; import { makeStyles } from "@material-ui/core"; +import red from "@material-ui/core/colors/red"; +import green from "@material-ui/core/colors/green"; const useStyles = makeStyles((theme) => ({ root: { @@ -8,12 +10,12 @@ const useStyles = makeStyles((theme) => ({ whiteSpace: "pre-wrap", }, add: { - color: "#22863a", - backgroundColor: "#f0fff4", + color: green[800], + backgroundColor: green[50], }, del: { - color: "#b31d28", - backgroundColor: "#ffeef0", + color: red[800], + backgroundColor: red[50], }, line: { minHeight: `${theme.typography.body2.lineHeight}em`, @@ -24,7 +26,7 @@ interface Props { content: string; } -export const DiffView: FC = ({ content }) => { +export const DiffView: FC = memo(function DiffView({ content }) { const classes = useStyles(); return (
@@ -32,7 +34,7 @@ export const DiffView: FC = ({ content }) => { switch (line[0]) { case "+": return ( -
+
{line} @@ -40,7 +42,7 @@ export const DiffView: FC = ({ content }) => { ); case "-": return ( -
+
{line}
); @@ -54,4 +56,4 @@ export const DiffView: FC = ({ content }) => { })}
); -}; +}); diff --git a/pkg/app/web/src/modules/active-stage.test.ts b/pkg/app/web/src/modules/active-stage.test.ts index 2478b1592d..f7d1d6e8b1 100644 --- a/pkg/app/web/src/modules/active-stage.test.ts +++ b/pkg/app/web/src/modules/active-stage.test.ts @@ -5,7 +5,7 @@ import { } from "./active-stage"; describe("activeStageSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( activeStageSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/api-keys.test.ts b/pkg/app/web/src/modules/api-keys.test.ts index 81ec71f6d6..00e9f525b8 100644 --- a/pkg/app/web/src/modules/api-keys.test.ts +++ b/pkg/app/web/src/modules/api-keys.test.ts @@ -19,7 +19,7 @@ const baseState = { }; describe("apiKeysSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( apiKeysSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/application-filter-options.test.ts b/pkg/app/web/src/modules/application-filter-options.test.ts index 149240b27f..0baa8a4203 100644 --- a/pkg/app/web/src/modules/application-filter-options.test.ts +++ b/pkg/app/web/src/modules/application-filter-options.test.ts @@ -6,7 +6,7 @@ import { } from "./application-filter-options"; describe("applicationFilterOptionsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( applicationFilterOptionsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/applications-live-state.test.ts b/pkg/app/web/src/modules/applications-live-state.test.ts index d28eb85830..58175b2f88 100644 --- a/pkg/app/web/src/modules/applications-live-state.test.ts +++ b/pkg/app/web/src/modules/applications-live-state.test.ts @@ -1,17 +1,70 @@ -import { applicationLiveStateSlice } from "./applications-live-state"; +import { dummyApplicationLiveState } from "../__fixtures__/dummy-application-live-state"; +import { + applicationLiveStateSlice, + ApplicationLiveStateState, + fetchApplicationStateById, +} from "./applications-live-state"; + +const initialState: ApplicationLiveStateState = { + entities: {}, + hasError: {}, + ids: [], +}; describe("applicationLiveStateSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( applicationLiveStateSlice.reducer(undefined, { type: "TEST_ACTION", }) - ).toMatchInlineSnapshot(` - Object { - "entities": Object {}, - "hasError": Object {}, - "ids": Array [], - } - `); + ).toEqual(initialState); + }); + + describe("fetchApplicationStateById", () => { + it(`should handle ${fetchApplicationStateById.pending.type}`, () => { + expect( + applicationLiveStateSlice.reducer(initialState, { + type: fetchApplicationStateById.pending.type, + meta: { + arg: "application-1", + }, + }) + ).toEqual({ ...initialState, hasError: { "application-1": false } }); + }); + + it(`should handle ${fetchApplicationStateById.rejected.type}`, () => { + expect( + applicationLiveStateSlice.reducer( + { ...initialState, hasError: { "application-1": false } }, + { + type: fetchApplicationStateById.rejected.type, + meta: { + arg: "application-1", + }, + } + ) + ).toEqual({ ...initialState, hasError: { "application-1": true } }); + }); + + it(`should handle ${fetchApplicationStateById.fulfilled.type}`, () => { + expect( + applicationLiveStateSlice.reducer( + { ...initialState, hasError: { "application-1": false } }, + { + type: fetchApplicationStateById.fulfilled.type, + meta: { + arg: "application-1", + }, + payload: dummyApplicationLiveState, + } + ) + ).toEqual({ + entities: { + [dummyApplicationLiveState.applicationId]: dummyApplicationLiveState, + }, + ids: [dummyApplicationLiveState.applicationId], + hasError: { "application-1": false }, + }); + }); }); }); diff --git a/pkg/app/web/src/modules/applications-live-state.ts b/pkg/app/web/src/modules/applications-live-state.ts index 11655222aa..cc16c4f419 100644 --- a/pkg/app/web/src/modules/applications-live-state.ts +++ b/pkg/app/web/src/modules/applications-live-state.ts @@ -44,8 +44,10 @@ const initialState = applicationLiveStateAdapter.getInitialState<{ hasError: {}, }); +export type ApplicationLiveStateState = typeof initialState; + export const selectHasError = ( - state: typeof initialState, + state: ApplicationLiveStateState, applicationId: string ): boolean => { return state.hasError[applicationId] || false; diff --git a/pkg/app/web/src/modules/applications.test.ts b/pkg/app/web/src/modules/applications.test.ts index 050fb416d9..da1515c22c 100644 --- a/pkg/app/web/src/modules/applications.test.ts +++ b/pkg/app/web/src/modules/applications.test.ts @@ -1,4 +1,7 @@ -import { dummyApplication } from "../__fixtures__/dummy-application"; +import { + dummyApplication, + dummyApplicationSyncState, +} from "../__fixtures__/dummy-application"; import { createStore } from "../../test-utils"; import { addApplication, @@ -54,7 +57,7 @@ describe("fetchApplications", () => { }); describe("applicationsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( applicationsSlice.reducer(undefined, { type: "TEST_ACTION", @@ -110,7 +113,7 @@ describe("applicationsSlice reducer", () => { const updatedApplication: Application = { ...dummyApplication, syncState: { - ...dummyApplication.syncState, + ...dummyApplicationSyncState, status: ApplicationSyncStatus.OUT_OF_SYNC, }, }; diff --git a/pkg/app/web/src/modules/applications.ts b/pkg/app/web/src/modules/applications.ts index d24007d31d..e498e57045 100644 --- a/pkg/app/web/src/modules/applications.ts +++ b/pkg/app/web/src/modules/applications.ts @@ -6,6 +6,7 @@ import { } from "@reduxjs/toolkit"; import { Application as ApplicationModel, + ApplicationSyncState as ApplicationSyncStateModel, ApplicationSyncStatus, } from "pipe/pkg/app/web/model/application_pb"; import * as applicationsAPI from "../api/applications"; @@ -20,6 +21,7 @@ import { AppState } from "."; export type Application = ApplicationModel.AsObject; export type ApplicationSyncStatusKey = keyof typeof ApplicationSyncStatus; export type ApplicationKindKey = keyof typeof ApplicationKind; +export type ApplicationSyncState = ApplicationSyncStateModel.AsObject; export const applicationsAdapter = createEntityAdapter({ selectId: (app) => app.id, diff --git a/pkg/app/web/src/modules/commands.test.ts b/pkg/app/web/src/modules/commands.test.ts index 03dd9e5f2c..c391cda832 100644 --- a/pkg/app/web/src/modules/commands.test.ts +++ b/pkg/app/web/src/modules/commands.test.ts @@ -38,7 +38,7 @@ describe("fetchCommand", () => { }); describe("commandsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( commandsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/deployment-filter-options.test.ts b/pkg/app/web/src/modules/deployment-filter-options.test.ts index ecc0fb914b..3691bff58c 100644 --- a/pkg/app/web/src/modules/deployment-filter-options.test.ts +++ b/pkg/app/web/src/modules/deployment-filter-options.test.ts @@ -14,7 +14,7 @@ const initialState = { }; describe("deploymentFilterOptionsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( deploymentFilterOptionsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/deployments.test.ts b/pkg/app/web/src/modules/deployments.test.ts index 73c017e7bf..06e0665783 100644 --- a/pkg/app/web/src/modules/deployments.test.ts +++ b/pkg/app/web/src/modules/deployments.test.ts @@ -48,7 +48,7 @@ test("isStageRunning", () => { }); describe("deploymentsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( deploymentsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/environments.test.ts b/pkg/app/web/src/modules/environments.test.ts index c62faa70b5..d4afbfbd6f 100644 --- a/pkg/app/web/src/modules/environments.test.ts +++ b/pkg/app/web/src/modules/environments.test.ts @@ -2,7 +2,7 @@ import { dummyEnv } from "../__fixtures__/dummy-environment"; import { environmentsSlice, fetchEnvironments } from "./environments"; describe("environmentsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( environmentsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/insight.test.ts b/pkg/app/web/src/modules/insight.test.ts index 7a36bb9a1b..8e760a260e 100644 --- a/pkg/app/web/src/modules/insight.test.ts +++ b/pkg/app/web/src/modules/insight.test.ts @@ -1,20 +1,64 @@ jest.spyOn(Date, "now").mockImplementation(() => 1); -import { insightSlice } from "./insight"; +import { + insightSlice, + InsightState, + changeApplication, + changeStep, + changeRangeFrom, + changeRangeTo, + InsightStep, +} from "./insight"; + +const initialState: InsightState = { + applicationId: "", + rangeFrom: 1, + rangeTo: 604800001, + step: 0, +}; describe("insightSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( insightSlice.reducer(undefined, { type: "TEST_ACTION", }) - ).toMatchInlineSnapshot(` - Object { - "applicationId": "", - "rangeFrom": 1, - "rangeTo": 604800001, - "step": 0, - } - `); + ).toEqual(initialState); + }); + + it(`should handle ${changeApplication.type}`, () => { + expect( + insightSlice.reducer(initialState, { + type: changeApplication.type, + payload: "application-1", + }) + ).toEqual({ ...initialState, applicationId: "application-1" }); + }); + + it(`should handle ${changeStep.type}`, () => { + expect( + insightSlice.reducer(initialState, { + type: changeStep.type, + payload: InsightStep.YEARLY, + }) + ).toEqual({ ...initialState, step: InsightStep.YEARLY }); + }); + + it(`should handle ${changeRangeFrom.type}`, () => { + expect( + insightSlice.reducer(initialState, { + type: changeRangeFrom.type, + payload: 2, + }) + ).toEqual({ ...initialState, rangeFrom: 2 }); + }); + + it(`should handle ${changeRangeTo.type}`, () => { + expect( + insightSlice.reducer(initialState, { + type: changeRangeTo.type, + payload: 3, + }) + ).toEqual({ ...initialState, rangeTo: 3 }); }); }); diff --git a/pkg/app/web/src/modules/insight.ts b/pkg/app/web/src/modules/insight.ts index b52113f4af..bca07c4eb1 100644 --- a/pkg/app/web/src/modules/insight.ts +++ b/pkg/app/web/src/modules/insight.ts @@ -9,7 +9,7 @@ export type InsightDataPoint = InsightDataPointModel.AsObject; const MODULE_NAME = "insight"; -interface Insight { +export interface InsightState { applicationId: string; step: InsightStep; rangeFrom: number; @@ -18,7 +18,7 @@ interface Insight { const now = dayjs(Date.now()); -const initialState: Insight = { +const initialState: InsightState = { applicationId: "", step: InsightStep.DAILY, rangeFrom: now.valueOf(), diff --git a/pkg/app/web/src/modules/login.test.ts b/pkg/app/web/src/modules/login.test.ts index f7277149df..07ba872106 100644 --- a/pkg/app/web/src/modules/login.test.ts +++ b/pkg/app/web/src/modules/login.test.ts @@ -1,7 +1,7 @@ import { loginSlice, clearProjectName, setProjectName } from "./login"; describe("loginSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( loginSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/me.test.ts b/pkg/app/web/src/modules/me.test.ts index 1788c04b19..c600d7ab97 100644 --- a/pkg/app/web/src/modules/me.test.ts +++ b/pkg/app/web/src/modules/me.test.ts @@ -32,7 +32,7 @@ describe("fetchMe", () => { }); describe("meSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( meSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/pipeds.test.ts b/pkg/app/web/src/modules/pipeds.test.ts index 1eaabf2b17..ec13705b7f 100644 --- a/pkg/app/web/src/modules/pipeds.test.ts +++ b/pkg/app/web/src/modules/pipeds.test.ts @@ -34,7 +34,7 @@ test("selectPipedsByEnv", () => { }); describe("pipedsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( pipedsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/project.test.ts b/pkg/app/web/src/modules/project.test.ts index a76cf167e6..ed8c06cf31 100644 --- a/pkg/app/web/src/modules/project.test.ts +++ b/pkg/app/web/src/modules/project.test.ts @@ -1,7 +1,7 @@ import { projectSlice, fetchProject, updateStaticAdmin } from "./project"; describe("projectSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( projectSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/stage-logs.test.ts b/pkg/app/web/src/modules/stage-logs.test.ts index 8605bbad7d..e3c1473ecc 100644 --- a/pkg/app/web/src/modules/stage-logs.test.ts +++ b/pkg/app/web/src/modules/stage-logs.test.ts @@ -13,7 +13,7 @@ test("createActiveStageKey", () => { }); describe("stageLogsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( stageLogsSlice.reducer(undefined, { type: "TEST_ACTION", diff --git a/pkg/app/web/src/modules/toasts.test.ts b/pkg/app/web/src/modules/toasts.test.ts index 415aafb12b..c64e028276 100644 --- a/pkg/app/web/src/modules/toasts.test.ts +++ b/pkg/app/web/src/modules/toasts.test.ts @@ -1,7 +1,7 @@ import { toastsSlice, addToast, removeToast } from "./toasts"; describe("toastsSlice reducer", () => { - it("should handle initial state", () => { + it("should return the initial state", () => { expect( toastsSlice.reducer(undefined, { type: "TEST_ACTION",