Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pre-submit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
- name: Lint
run: make lint

- name: Test
run: make test
Comment thread
razo7 marked this conversation as resolved.

- name: clean
run: make clean

Expand Down
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SDK 4.23 prerelease declares react-router ~7.13.1 as peerOptional,
# but console release-4.23 ships ~7.18.1. Remove when SDK catches up.
legacy-peer-deps=true
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ IMG ?= $(IMAGE_REGISTRY)/node-remediation-console:$(IMAGE_TAG)
lint:
npm ci && npm run lint

# Run tests
.PHONY: test
test:
npm run test
Comment thread
razo7 marked this conversation as resolved.

# Clean node_modules and npm cache to avoid disk space issues
.PHONY: clean
clean:
Expand Down
4,612 changes: 927 additions & 3,685 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 11 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@
"i18next": "i18next \"src/**/*.{js,jsx,ts,tsx}\" [-oc] -c i18next-parser.config.js",
"ts-node": "ts-node -O '{\"module\":\"commonjs\"}'",
"lint": "eslint src && stylelint \"src/**/*.css\" --allow-empty-input",
"test": "vitest run",
"test:watch": "vitest",
"http-server": "./http-server.sh dist"
},
"devDependencies": {
"@babel/core": "^8.0.1",
"@babel/preset-env": "^8.0.2",
"@babel/preset-react": "^8.0.1",
"@babel/preset-typescript": "^8.0.1",
"@openshift-console/dynamic-plugin-sdk": "^4.22.0-prerelease.3",
"@openshift-console/dynamic-plugin-sdk-internal": "^4.22.0-prerelease.3",
"@openshift-console/dynamic-plugin-sdk-webpack": "^4.22.0-prerelease.3",
"@patternfly/react-core": "~6.4.2",
"@patternfly/react-icons": "~6.4.0",
"@patternfly/react-table": "~6.4.2",
"@patternfly/react-tokens": "~6.4.0",
"@openshift-console/dynamic-plugin-sdk": "^4.23.0-prerelease.4",
"@openshift-console/dynamic-plugin-sdk-internal": "^4.23.0-prerelease.4",
"@openshift-console/dynamic-plugin-sdk-webpack": "^4.23.0-prerelease.4",
"@patternfly/react-core": "~6.6.0",
"@patternfly/react-icons": "~6.6.0",
"@patternfly/react-table": "~6.6.0",
"@patternfly/react-tokens": "~6.6.0",
"@types/classnames": "2.2.7",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "4.14.182",
Expand Down Expand Up @@ -56,7 +58,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "~16.5.8",
"react-router": "~7.13.1",
"react-router": "~7.18.1",
"sass": "^1.42.1",
"showdown": "2.1.0",
"style-loader": "^4.0.0",
Expand All @@ -66,6 +68,7 @@
"ts-loader": "^9.5.4",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vitest": "^3.2.0",
"webpack": "^5.100.0",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.5"
Expand Down
125 changes: 125 additions & 0 deletions src/data/__tests__/dependency-imports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { describe, it, expect } from "vitest";

describe("PatternFly react-core imports", () => {
it("resolves layout components", async () => {
const mod = await import("@patternfly/react-core");
expect(mod.PageSection).toBeDefined();
expect(mod.Stack).toBeDefined();
expect(mod.StackItem).toBeDefined();
expect(mod.Grid).toBeDefined();
expect(mod.GridItem).toBeDefined();
expect(mod.Flex).toBeDefined();
expect(mod.FlexItem).toBeDefined();
expect(mod.Bullseye).toBeDefined();
});

it("resolves form components", async () => {
const mod = await import("@patternfly/react-core");
expect(mod.Form).toBeDefined();
expect(mod.FormGroup).toBeDefined();
expect(mod.FormSection).toBeDefined();
expect(mod.TextInput).toBeDefined();
expect(mod.TextInputTypes).toBeDefined();
expect(mod.Radio).toBeDefined();
expect(mod.NumberInput).toBeDefined();
expect(mod.Checkbox).toBeDefined();
expect(mod.FormHelperText).toBeDefined();
});

it("resolves modal components", async () => {
const mod = await import("@patternfly/react-core");
expect(mod.Modal).toBeDefined();
expect(mod.ModalVariant).toBeDefined();
expect(mod.ModalHeader).toBeDefined();
expect(mod.ModalBody).toBeDefined();
expect(mod.ModalFooter).toBeDefined();
});

it("resolves feedback components", async () => {
const mod = await import("@patternfly/react-core");
expect(mod.Alert).toBeDefined();
expect(mod.AlertVariant).toBeDefined();
expect(mod.Popover).toBeDefined();
expect(mod.Tooltip).toBeDefined();
});

it("resolves select components", async () => {
const mod = await import("@patternfly/react-core");
expect(mod.Select).toBeDefined();
expect(mod.SelectOption).toBeDefined();
expect(mod.MenuToggle).toBeDefined();
expect(mod.SelectList).toBeDefined();
expect(mod.Divider).toBeDefined();
});

it("resolves misc components", async () => {
const mod = await import("@patternfly/react-core");
expect(mod.Button).toBeDefined();
expect(mod.ButtonVariant).toBeDefined();
expect(mod.Label).toBeDefined();
expect(mod.Title).toBeDefined();
expect(mod.Content).toBeDefined();
expect(mod.DescriptionList).toBeDefined();
expect(mod.EmptyState).toBeDefined();
expect(mod.ExpandableSection).toBeDefined();
expect(mod.Icon).toBeDefined();
expect(mod.List).toBeDefined();
expect(mod.ListItem).toBeDefined();
});
});

describe("PatternFly react-icons imports", () => {
it("resolves status icons", async () => {
const mod = await import("@patternfly/react-icons");
expect(mod.CheckCircleIcon).toBeDefined();
expect(mod.ExclamationCircleIcon).toBeDefined();
expect(mod.ExclamationTriangleIcon).toBeDefined();
expect(mod.InfoCircleIcon).toBeDefined();
expect(mod.HourglassHalfIcon).toBeDefined();
expect(mod.InProgressIcon).toBeDefined();
});

it("resolves action icons", async () => {
const mod = await import("@patternfly/react-icons");
expect(mod.PlusCircleIcon).toBeDefined();
expect(mod.MinusCircleIcon).toBeDefined();
expect(mod.PencilAltIcon).toBeDefined();
expect(mod.EllipsisVIcon).toBeDefined();
expect(mod.DownloadIcon).toBeDefined();
expect(mod.ExternalLinkAltIcon).toBeDefined();
expect(mod.SearchIcon).toBeDefined();
expect(mod.OutlinedQuestionCircleIcon).toBeDefined();
});
});

describe("PatternFly react-table imports", () => {
it("resolves table components and utilities", async () => {
const mod = await import("@patternfly/react-table");
expect(mod.Table).toBeDefined();
expect(mod.Thead).toBeDefined();
expect(mod.Tbody).toBeDefined();
expect(mod.Tr).toBeDefined();
expect(mod.Th).toBeDefined();
expect(mod.Td).toBeDefined();
expect(mod.sortable).toBeDefined();
expect(mod.SortByDirection).toBeDefined();
});
});

describe("PatternFly react-tokens imports", () => {
it("resolves design tokens used in the project", async () => {
const mod = await import("@patternfly/react-tokens");
expect(mod.t_global_icon_color_status_info_default).toBeDefined();
expect(mod.t_global_text_color_link_default).toBeDefined();
expect(mod.t_global_color_status_danger_100).toBeDefined();
});
});

describe("react-router imports", () => {
it("resolves navigation hooks and components", async () => {
const mod = await import("react-router");
expect(mod.useNavigate).toBeDefined();
expect(mod.useLocation).toBeDefined();
expect(mod.Link).toBeDefined();
});
});
134 changes: 134 additions & 0 deletions src/data/__tests__/model.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { describe, it, expect } from "vitest";
import {
getStringKind,
getApiVersion,
parseApiVersion,
apiVersionToGroupVersionKind,
getCreateInstanceUrl,
nodeHealthCheckKind,
nodeHealthCheckStringKind,
NodeHealthCheckModel,
getNodeHealthCheckApiVersion,
nodeKind,
} from "../model";

describe("getStringKind", () => {
it("joins group, version, and kind with tildes", () => {
expect(
getStringKind({ group: "apps", version: "v1", kind: "Deployment" })
).toBe("apps~v1~Deployment");
});

it("handles empty group", () => {
expect(getStringKind({ group: "", version: "v1", kind: "Pod" })).toBe(
"~v1~Pod"
);
});
});

describe("getApiVersion", () => {
it("joins group and version with slash", () => {
expect(
getApiVersion({ group: "apps", version: "v1", kind: "Deployment" })
).toBe("apps/v1");
});
});

describe("parseApiVersion", () => {
it("parses valid apiVersion", () => {
expect(parseApiVersion("apps/v1")).toEqual({
group: "apps",
version: "v1",
});
});

it("returns undefined for empty string", () => {
expect(parseApiVersion("")).toBeUndefined();
});

it("parses a core-group apiVersion", () => {
expect(parseApiVersion("v1")).toEqual({ group: "", version: "v1" });
});

it("returns undefined for string with multiple slashes", () => {
expect(parseApiVersion("a/b/c")).toBeUndefined();
});

it("returns undefined for slash-only parts", () => {
expect(parseApiVersion("/v1")).toBeUndefined();
expect(parseApiVersion("apps/")).toBeUndefined();
});
});

describe("apiVersionToGroupVersionKind", () => {
it("converts valid apiVersion and kind", () => {
expect(apiVersionToGroupVersionKind("apps/v1", "Deployment")).toEqual({
group: "apps",
version: "v1",
kind: "Deployment",
});
});

it("returns undefined for empty apiVersion", () => {
expect(apiVersionToGroupVersionKind("", "Pod")).toBeUndefined();
});

it("returns undefined for empty kind", () => {
expect(apiVersionToGroupVersionKind("apps/v1", "")).toBeUndefined();
});

it("converts core-group apiVersion", () => {
expect(apiVersionToGroupVersionKind("v1", "Pod")).toEqual({
group: "",
version: "v1",
kind: "Pod",
});
});
});

describe("getCreateInstanceUrl", () => {
it("builds correct URL", () => {
expect(getCreateInstanceUrl("apps/v1", "Deployment")).toBe(
"/k8s/cluster/apps~v1~Deployment/~new"
);
});

it("returns undefined for empty inputs", () => {
expect(getCreateInstanceUrl("", "Pod")).toBeUndefined();
expect(getCreateInstanceUrl("apps/v1", "")).toBeUndefined();
});
});

describe("NHC constants", () => {
it("nodeHealthCheckKind has correct values", () => {
expect(nodeHealthCheckKind).toEqual({
kind: "NodeHealthCheck",
group: "remediation.medik8s.io",
version: "v1alpha1",
});
});

it("nodeHealthCheckStringKind is derived correctly", () => {
expect(nodeHealthCheckStringKind).toBe(
"remediation.medik8s.io~v1alpha1~NodeHealthCheck"
);
});

it("nodeKind has correct values", () => {
expect(nodeKind).toEqual({ kind: "Node", version: "v1" });
});

it("NodeHealthCheckModel is consistent with nodeHealthCheckKind", () => {
expect(NodeHealthCheckModel.kind).toBe(nodeHealthCheckKind.kind);
expect(NodeHealthCheckModel.apiGroup).toBe(nodeHealthCheckKind.group);
expect(NodeHealthCheckModel.apiVersion).toBe(nodeHealthCheckKind.version);
expect(NodeHealthCheckModel.namespaced).toBe(false);
expect(NodeHealthCheckModel.plural).toBe("nodehealthchecks");
});

it("getNodeHealthCheckApiVersion returns correct string", () => {
expect(getNodeHealthCheckApiVersion()).toBe(
"remediation.medik8s.io/v1alpha1"
);
});
});
Loading
Loading