mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-15 20:59:18 +00:00
521 lines
14 KiB
TypeScript
521 lines
14 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
|
import { nextTick } from "vue";
|
|
import Radio from "../Radio.vue";
|
|
|
|
describe("Radio Component", () => {
|
|
let wrapper: Recordable;
|
|
|
|
const mockOptions = [
|
|
{ label: "Option 1", value: "option1" },
|
|
{ label: "Option 2", value: "option2" },
|
|
{ label: "Option 3", value: "option3" },
|
|
];
|
|
|
|
const mockOptionsWithNumbers = [
|
|
{ label: "Option 1", value: 1 },
|
|
{ label: "Option 2", value: 2 },
|
|
{ label: "Option 3", value: 3 },
|
|
];
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("Props", () => {
|
|
it("should render with default props", () => {
|
|
wrapper = mount(Radio);
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.selected).toBe("");
|
|
expect(wrapper.vm.options).toEqual([]);
|
|
expect(wrapper.vm.size).toBe("default");
|
|
});
|
|
|
|
it("should render with custom options", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(mockOptions);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(3);
|
|
});
|
|
|
|
it("should render with custom value", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
value: "option2",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.selected).toBe("option2");
|
|
});
|
|
|
|
it("should render with custom size", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
size: "small",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.size).toBe("small");
|
|
});
|
|
|
|
it("should handle options with number values", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptionsWithNumbers,
|
|
value: "2",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(mockOptionsWithNumbers);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(3);
|
|
});
|
|
|
|
it("should handle empty options array", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: [],
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual([]);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(0);
|
|
});
|
|
|
|
it("should handle undefined options", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: undefined,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("Rendering", () => {
|
|
it("should render el-radio-group", () => {
|
|
wrapper = mount(Radio);
|
|
|
|
expect(wrapper.find(".el-radio-group").exists()).toBe(true);
|
|
});
|
|
|
|
it("should render correct number of radio options", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
expect(radioElements).toHaveLength(3);
|
|
});
|
|
|
|
it("should render radio labels correctly", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
expect(radioElements[0].text()).toContain("Option 1");
|
|
expect(radioElements[1].text()).toContain("Option 2");
|
|
expect(radioElements[2].text()).toContain("Option 3");
|
|
});
|
|
|
|
it("should set correct key attributes", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
// Vue doesn't expose key attributes directly, but we can verify the structure
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
expect(radioElements).toHaveLength(3);
|
|
// Verify that each radio has a unique structure based on the options
|
|
expect(radioElements[0].text()).toContain("Option 1");
|
|
expect(radioElements[1].text()).toContain("Option 2");
|
|
expect(radioElements[2].text()).toContain("Option 3");
|
|
});
|
|
|
|
it("should set correct label attributes", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
// Vue doesn't expose label attributes directly, but we can verify the structure
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
expect(radioElements).toHaveLength(3);
|
|
// Verify that each radio has the correct text content
|
|
expect(radioElements[0].text()).toContain("Option 1");
|
|
expect(radioElements[1].text()).toContain("Option 2");
|
|
expect(radioElements[2].text()).toContain("Option 3");
|
|
});
|
|
|
|
it("should handle mixed string and number labels", () => {
|
|
const mixedOptions = [
|
|
{ label: "String Label", value: "string" },
|
|
{ label: 123, value: "number" },
|
|
];
|
|
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mixedOptions,
|
|
},
|
|
});
|
|
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
expect(radioElements[0].text()).toContain("String Label");
|
|
expect(radioElements[1].text()).toContain("123");
|
|
});
|
|
});
|
|
|
|
describe("Events", () => {
|
|
it("should emit change event when radio is selected", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
// Trigger the change event by calling the checked function directly
|
|
await wrapper.vm.checked("option2");
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted("change")).toBeTruthy();
|
|
expect(wrapper.emitted("change")[0]).toEqual(["option2"]);
|
|
});
|
|
|
|
it("should emit change event with correct value", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
await wrapper.vm.checked("option3");
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted("change")[0]).toEqual(["option3"]);
|
|
});
|
|
|
|
it("should emit change event multiple times", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
await wrapper.vm.checked("option1");
|
|
await nextTick();
|
|
await wrapper.vm.checked("option2");
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted("change")).toHaveLength(2);
|
|
expect(wrapper.emitted("change")[0]).toEqual(["option1"]);
|
|
expect(wrapper.emitted("change")[1]).toEqual(["option2"]);
|
|
});
|
|
|
|
it("should handle change event with number value", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptionsWithNumbers,
|
|
},
|
|
});
|
|
|
|
await wrapper.vm.checked(2);
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted("change")[0]).toEqual([2]);
|
|
});
|
|
});
|
|
|
|
describe("Data Binding", () => {
|
|
it("should update selected value when props change", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
value: "option1",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.selected).toBe("option1");
|
|
|
|
await wrapper.setProps({ value: "option2" });
|
|
await nextTick();
|
|
|
|
expect(wrapper.vm.selected).toBe("option2");
|
|
});
|
|
|
|
it("should update options when props change", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(mockOptions);
|
|
|
|
const newOptions = [
|
|
{ label: "New Option 1", value: "new1" },
|
|
{ label: "New Option 2", value: "new2" },
|
|
];
|
|
|
|
await wrapper.setProps({ options: newOptions });
|
|
await nextTick();
|
|
|
|
expect(wrapper.vm.options).toEqual(newOptions);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(2);
|
|
});
|
|
|
|
it("should maintain selected value when options change", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
value: "option2",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.selected).toBe("option2");
|
|
|
|
const newOptions = [
|
|
{ label: "New Option 1", value: "new1" },
|
|
{ label: "New Option 2", value: "new2" },
|
|
];
|
|
|
|
await wrapper.setProps({ options: newOptions });
|
|
await nextTick();
|
|
|
|
expect(wrapper.vm.selected).toBe("option2"); // Should maintain the selected value
|
|
});
|
|
});
|
|
|
|
describe("Edge Cases", () => {
|
|
it("should handle null options", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: null as any,
|
|
},
|
|
});
|
|
|
|
// When null is passed, Vue will pass it through as-is
|
|
// The component should handle this gracefully in the template
|
|
expect(wrapper.vm.options).toBeNull();
|
|
// But the component should still render without errors
|
|
expect(wrapper.find(".el-radio-group").exists()).toBe(true);
|
|
});
|
|
|
|
it("should handle undefined value", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
value: undefined,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.selected).toBe("");
|
|
});
|
|
|
|
it("should handle empty string value", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
value: "",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.selected).toBe("");
|
|
});
|
|
|
|
it("should handle options with empty labels", () => {
|
|
const optionsWithEmptyLabels = [
|
|
{ label: "", value: "empty" },
|
|
{ label: "Valid Label", value: "valid" },
|
|
];
|
|
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: optionsWithEmptyLabels,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(optionsWithEmptyLabels);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(2);
|
|
});
|
|
|
|
it("should handle options with empty values", () => {
|
|
const optionsWithEmptyValues = [
|
|
{ label: "Label 1", value: "" },
|
|
{ label: "Label 2", value: "valid" },
|
|
];
|
|
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: optionsWithEmptyValues,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(optionsWithEmptyValues);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(2);
|
|
});
|
|
|
|
it("should handle very long labels", () => {
|
|
const longLabel = "A".repeat(1000);
|
|
const optionsWithLongLabel = [{ label: longLabel, value: "long" }];
|
|
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: optionsWithLongLabel,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(optionsWithLongLabel);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(1);
|
|
});
|
|
|
|
it("should handle special characters in labels", () => {
|
|
const specialOptions = [
|
|
{ label: "Option with & symbols", value: "special1" },
|
|
{ label: "Option with <script> tags", value: "special2" },
|
|
{ label: "Option with 'quotes'", value: "special3" },
|
|
];
|
|
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: specialOptions,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(specialOptions);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(3);
|
|
});
|
|
});
|
|
|
|
describe("Component Integration", () => {
|
|
it("should work with Element Plus radio components", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
const radioGroup = wrapper.find(".el-radio-group");
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
|
|
expect(radioGroup.exists()).toBe(true);
|
|
expect(radioElements.length).toBeGreaterThan(0);
|
|
// Verify the component structure is correct
|
|
expect(wrapper.vm.selected).toBe("");
|
|
});
|
|
|
|
it("should have correct v-model binding", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
value: "option1",
|
|
},
|
|
});
|
|
|
|
// Verify the internal selected value matches the prop
|
|
expect(wrapper.vm.selected).toBe("option1");
|
|
});
|
|
|
|
it("should have correct change event binding", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
// Verify the component has the checked function
|
|
expect(typeof wrapper.vm.checked).toBe("function");
|
|
});
|
|
});
|
|
|
|
describe("Accessibility", () => {
|
|
it("should have proper ARIA attributes", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
const radioGroup = wrapper.find(".el-radio-group");
|
|
expect(radioGroup.exists()).toBe(true);
|
|
});
|
|
|
|
it("should render radio options with proper structure", () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
},
|
|
});
|
|
|
|
const radioElements = wrapper.findAll(".el-radio");
|
|
radioElements.forEach((radio: VueWrapper) => {
|
|
expect(radio.exists()).toBe(true);
|
|
expect(radio.text()).toBeTruthy();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Performance", () => {
|
|
it("should handle large number of options", () => {
|
|
const largeOptions = Array.from({ length: 100 }, (_, i) => ({
|
|
label: `Option ${i + 1}`,
|
|
value: `option${i + 1}`,
|
|
}));
|
|
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: largeOptions,
|
|
},
|
|
});
|
|
|
|
expect(wrapper.vm.options).toEqual(largeOptions);
|
|
expect(wrapper.findAll(".el-radio")).toHaveLength(100);
|
|
});
|
|
|
|
it("should handle rapid prop changes", async () => {
|
|
wrapper = mount(Radio, {
|
|
props: {
|
|
options: mockOptions,
|
|
value: "option1",
|
|
},
|
|
});
|
|
|
|
// Rapidly change props
|
|
await wrapper.setProps({ value: "option2" });
|
|
await wrapper.setProps({ value: "option3" });
|
|
await wrapper.setProps({ value: "option1" });
|
|
await nextTick();
|
|
|
|
expect(wrapper.vm.selected).toBe("option1");
|
|
});
|
|
});
|
|
});
|