From 7fc501915eb7205c1ce93d04b7ff30c5c8f26b15 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:21:34 +0800 Subject: [PATCH] test: add comprehensive frontend tests for billing plan assets (#29615) Signed-off-by: yyh Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../billing/plan/assets/enterprise.spec.tsx | 249 ++++++++++++++ .../billing/plan/assets/index.spec.tsx | 312 ++++++++++++++++++ .../billing/plan/assets/professional.spec.tsx | 172 ++++++++++ .../billing/plan/assets/sandbox.spec.tsx | 128 +++++++ .../billing/plan/assets/team.spec.tsx | 199 +++++++++++ 5 files changed, 1060 insertions(+) create mode 100644 web/app/components/billing/plan/assets/enterprise.spec.tsx create mode 100644 web/app/components/billing/plan/assets/index.spec.tsx create mode 100644 web/app/components/billing/plan/assets/professional.spec.tsx create mode 100644 web/app/components/billing/plan/assets/sandbox.spec.tsx create mode 100644 web/app/components/billing/plan/assets/team.spec.tsx diff --git a/web/app/components/billing/plan/assets/enterprise.spec.tsx b/web/app/components/billing/plan/assets/enterprise.spec.tsx new file mode 100644 index 0000000000..831370f5d9 --- /dev/null +++ b/web/app/components/billing/plan/assets/enterprise.spec.tsx @@ -0,0 +1,249 @@ +import { render } from '@testing-library/react' +import Enterprise from './enterprise' + +describe('Enterprise Icon Component', () => { + describe('Rendering', () => { + it('should render without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('should render an SVG element', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should have correct SVG attributes', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toHaveAttribute('xmlns', 'http://www.w3.org/2000/svg') + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + expect(svg).toHaveAttribute('viewBox', '0 0 32 32') + expect(svg).toHaveAttribute('fill', 'none') + }) + + it('should render only path elements', () => { + const { container } = render() + const paths = container.querySelectorAll('path') + const rects = container.querySelectorAll('rect') + + // Enterprise icon uses only path elements, no rects + expect(paths.length).toBeGreaterThan(0) + expect(rects).toHaveLength(0) + }) + + it('should render elements with correct fill colors', () => { + const { container } = render() + const blueElements = container.querySelectorAll('[fill="var(--color-saas-dify-blue-inverted)"]') + const quaternaryElements = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + expect(blueElements.length).toBeGreaterThan(0) + expect(quaternaryElements.length).toBeGreaterThan(0) + }) + }) + + describe('Component Behavior', () => { + it('should render consistently across multiple renders', () => { + const { container: container1 } = render() + const { container: container2 } = render() + + expect(container1.innerHTML).toBe(container2.innerHTML) + }) + + it('should maintain stable output without memoization', () => { + const { container, rerender } = render() + const firstRender = container.innerHTML + + rerender() + const secondRender = container.innerHTML + + expect(firstRender).toBe(secondRender) + }) + + it('should be a functional component', () => { + expect(typeof Enterprise).toBe('function') + }) + }) + + describe('Accessibility', () => { + it('should render as a decorative image', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + }) + + it('should be usable in accessible contexts', () => { + const { container } = render( +
+ +
, + ) + + const wrapper = container.querySelector('[role="img"]') + expect(wrapper).toBeInTheDocument() + expect(wrapper).toHaveAttribute('aria-label', 'Enterprise plan') + }) + + it('should support custom wrapper accessibility', () => { + const { container } = render( + , + ) + + const button = container.querySelector('button') + expect(button).toHaveAttribute('aria-label', 'Select Enterprise plan') + }) + }) + + describe('Edge Cases', () => { + it('should handle multiple instances without conflicts', () => { + const { container } = render( + <> + + + + , + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(3) + }) + + it('should maintain structure when wrapped in other elements', () => { + const { container } = render( +
+ + + +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + expect(svg?.getAttribute('width')).toBe('32') + }) + + it('should render correctly in grid layout', () => { + const { container } = render( +
+ +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should render correctly in flex layout', () => { + const { container } = render( +
+ +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + }) + + describe('CSS Variables', () => { + it('should use CSS custom properties for colors', () => { + const { container } = render() + const elementsWithCSSVars = container.querySelectorAll('[fill*="var("]') + + expect(elementsWithCSSVars.length).toBeGreaterThan(0) + }) + + it('should have opacity attributes on quaternary path elements', () => { + const { container } = render() + const quaternaryPaths = container.querySelectorAll('path[fill="var(--color-text-quaternary)"]') + + quaternaryPaths.forEach((path) => { + expect(path).toHaveAttribute('opacity', '0.18') + }) + }) + + it('should not have opacity on blue inverted path elements', () => { + const { container } = render() + const bluePaths = container.querySelectorAll('path[fill="var(--color-saas-dify-blue-inverted)"]') + + bluePaths.forEach((path) => { + expect(path).not.toHaveAttribute('opacity') + }) + }) + + it('should use correct CSS variable names', () => { + const { container } = render() + const paths = container.querySelectorAll('path') + + paths.forEach((path) => { + const fill = path.getAttribute('fill') + if (fill?.includes('var(')) + expect(fill).toMatch(/var\(--(color-saas-dify-blue-inverted|color-text-quaternary)\)/) + }) + }) + }) + + describe('SVG Structure', () => { + it('should have correct path element structure', () => { + const { container } = render() + const paths = container.querySelectorAll('path') + + paths.forEach((path) => { + expect(path).toHaveAttribute('d') + expect(path).toHaveAttribute('fill') + }) + }) + + it('should have valid path data', () => { + const { container } = render() + const paths = container.querySelectorAll('path') + + paths.forEach((path) => { + const d = path.getAttribute('d') + expect(d).toBeTruthy() + expect(d?.length).toBeGreaterThan(0) + }) + }) + + it('should maintain proper element count', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg?.childNodes.length).toBeGreaterThan(0) + }) + }) + + describe('Export', () => { + it('should be the default export', () => { + expect(Enterprise).toBeDefined() + expect(typeof Enterprise).toBe('function') + }) + + it('should return valid JSX', () => { + const result = Enterprise() + expect(result).toBeTruthy() + expect(result.type).toBe('svg') + }) + }) + + describe('Performance', () => { + it('should render efficiently for multiple instances', () => { + const { container } = render( +
+ {Array.from({ length: 10 }).map((_, i) => ( + + ))} +
, + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(10) + }) + }) +}) diff --git a/web/app/components/billing/plan/assets/index.spec.tsx b/web/app/components/billing/plan/assets/index.spec.tsx new file mode 100644 index 0000000000..b9b8763fb0 --- /dev/null +++ b/web/app/components/billing/plan/assets/index.spec.tsx @@ -0,0 +1,312 @@ +import { render } from '@testing-library/react' +import { Enterprise, Professional, Sandbox, Team } from './index' + +// Import real components for comparison +import SandboxDirect from './sandbox' +import ProfessionalDirect from './professional' +import TeamDirect from './team' +import EnterpriseDirect from './enterprise' + +describe('Billing Plan Assets - Integration Tests', () => { + describe('Exports', () => { + it('should export Sandbox component', () => { + expect(Sandbox).toBeDefined() + // Sandbox is wrapped with React.memo, so it's an object + expect(typeof Sandbox).toMatch(/function|object/) + }) + + it('should export Professional component', () => { + expect(Professional).toBeDefined() + expect(typeof Professional).toBe('function') + }) + + it('should export Team component', () => { + expect(Team).toBeDefined() + expect(typeof Team).toBe('function') + }) + + it('should export Enterprise component', () => { + expect(Enterprise).toBeDefined() + expect(typeof Enterprise).toBe('function') + }) + + it('should export all four components', () => { + const exports = { Sandbox, Professional, Team, Enterprise } + expect(Object.keys(exports)).toHaveLength(4) + }) + }) + + describe('Export Integrity', () => { + it('should export the correct Sandbox component', () => { + expect(Sandbox).toBe(SandboxDirect) + }) + + it('should export the correct Professional component', () => { + expect(Professional).toBe(ProfessionalDirect) + }) + + it('should export the correct Team component', () => { + expect(Team).toBe(TeamDirect) + }) + + it('should export the correct Enterprise component', () => { + expect(Enterprise).toBe(EnterpriseDirect) + }) + }) + + describe('Rendering Integration', () => { + it('should render all components without conflicts', () => { + const { container } = render( +
+ + + + +
, + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(4) + }) + + it('should render Sandbox component correctly', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + }) + + it('should render Professional component correctly', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + }) + + it('should render Team component correctly', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + }) + + it('should render Enterprise component correctly', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + }) + }) + + describe('Visual Consistency', () => { + it('should maintain consistent SVG dimensions across all components', () => { + const components = [ + , + , + , + , + ] + + components.forEach((component) => { + const { container } = render(component) + const svg = container.querySelector('svg') + + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + expect(svg).toHaveAttribute('viewBox', '0 0 32 32') + }) + }) + + it('should use consistent color variables across all components', () => { + const components = [Sandbox, Professional, Team, Enterprise] + + components.forEach((Component) => { + const { container } = render() + const elementsWithBlue = container.querySelectorAll('[fill="var(--color-saas-dify-blue-inverted)"]') + const elementsWithQuaternary = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + expect(elementsWithBlue.length).toBeGreaterThan(0) + expect(elementsWithQuaternary.length).toBeGreaterThan(0) + }) + }) + }) + + describe('Component Independence', () => { + it('should render components independently without side effects', () => { + const { container: container1 } = render() + const svg1 = container1.querySelector('svg') + + const { container: container2 } = render() + const svg2 = container2.querySelector('svg') + + // Components should not affect each other + expect(svg1).toBeInTheDocument() + expect(svg2).toBeInTheDocument() + expect(svg1).not.toBe(svg2) + }) + + it('should allow selective imports', () => { + // Verify that importing only one component works + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + }) + }) + + describe('Bundle Export Pattern', () => { + it('should follow barrel export pattern correctly', () => { + // All exports should be available from the index + expect(Sandbox).toBeDefined() + expect(Professional).toBeDefined() + expect(Team).toBeDefined() + expect(Enterprise).toBeDefined() + }) + + it('should maintain tree-shaking compatibility', () => { + // Each export should be independently usable + const components = [Sandbox, Professional, Team, Enterprise] + + components.forEach((Component) => { + // Component can be function or object (React.memo wraps it) + expect(['function', 'object']).toContain(typeof Component) + const { container } = render() + expect(container.querySelector('svg')).toBeInTheDocument() + }) + }) + }) + + describe('Real-world Usage Patterns', () => { + it('should support rendering in a plan selector', () => { + const { container } = render( +
+ + + + +
, + ) + + const svgs = container.querySelectorAll('svg') + const buttons = container.querySelectorAll('button') + + expect(svgs).toHaveLength(4) + expect(buttons).toHaveLength(4) + }) + + it('should support rendering in a comparison table', () => { + const { container } = render( + + + + + + + + + +
, + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(4) + }) + + it('should support conditional rendering', () => { + const renderPlan = (planType: 'sandbox' | 'professional' | 'team' | 'enterprise') => ( +
+ {planType === 'sandbox' && } + {planType === 'professional' && } + {planType === 'team' && } + {planType === 'enterprise' && } +
+ ) + + const { container } = render(renderPlan('team')) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(1) + }) + + it('should support dynamic rendering from array', () => { + const plans = [ + { id: 'sandbox', Icon: Sandbox }, + { id: 'professional', Icon: Professional }, + { id: 'team', Icon: Team }, + { id: 'enterprise', Icon: Enterprise }, + ] + + const { container } = render( +
+ {plans.map(({ id, Icon }) => ( +
+ +
+ ))} +
, + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(4) + }) + }) + + describe('Performance', () => { + it('should handle rapid re-renders efficiently', () => { + const { container, rerender } = render( +
+ + +
, + ) + + // Simulate multiple re-renders + for (let i = 0; i < 5; i++) { + rerender( +
+ + +
, + ) + } + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(2) + }) + + it('should handle large lists efficiently', () => { + const { container } = render( +
+ {Array.from({ length: 20 }).map((_, i) => { + const components = [Sandbox, Professional, Team, Enterprise] + const Component = components[i % 4] + return + })} +
, + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(20) + }) + }) +}) diff --git a/web/app/components/billing/plan/assets/professional.spec.tsx b/web/app/components/billing/plan/assets/professional.spec.tsx new file mode 100644 index 0000000000..0fb84e2870 --- /dev/null +++ b/web/app/components/billing/plan/assets/professional.spec.tsx @@ -0,0 +1,172 @@ +import { render } from '@testing-library/react' +import Professional from './professional' + +describe('Professional Icon Component', () => { + describe('Rendering', () => { + it('should render without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('should render an SVG element', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should have correct SVG attributes', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toHaveAttribute('xmlns', 'http://www.w3.org/2000/svg') + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + expect(svg).toHaveAttribute('viewBox', '0 0 32 32') + expect(svg).toHaveAttribute('fill', 'none') + }) + + it('should render correct number of SVG rect elements', () => { + const { container } = render() + const rects = container.querySelectorAll('rect') + + // Based on the component structure, it should have multiple rect elements + expect(rects.length).toBeGreaterThan(0) + }) + + it('should render elements with correct fill colors', () => { + const { container } = render() + const blueElements = container.querySelectorAll('[fill="var(--color-saas-dify-blue-inverted)"]') + const quaternaryElements = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + expect(blueElements.length).toBeGreaterThan(0) + expect(quaternaryElements.length).toBeGreaterThan(0) + }) + }) + + describe('Component Behavior', () => { + it('should render consistently across multiple renders', () => { + const { container: container1 } = render() + const { container: container2 } = render() + + expect(container1.innerHTML).toBe(container2.innerHTML) + }) + + it('should not be wrapped with React.memo', () => { + // Professional component is exported directly without React.memo + // This test ensures the component renders correctly without memoization + const { container, rerender } = render() + const firstRender = container.innerHTML + + rerender() + const secondRender = container.innerHTML + + // Content should still be the same even without memoization + expect(firstRender).toBe(secondRender) + }) + }) + + describe('Accessibility', () => { + it('should render as a decorative image (no accessibility concerns for icon)', () => { + const { container } = render() + const svg = container.querySelector('svg') + + // SVG icons typically don't need aria-labels if they're decorative + // This test ensures the SVG is present and renderable + expect(svg).toBeInTheDocument() + }) + }) + + describe('Edge Cases', () => { + it('should handle multiple instances without conflicts', () => { + const { container } = render( + <> + + + + , + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(3) + }) + + it('should maintain structure when wrapped in other elements', () => { + const { container } = render( +
+ + + +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + expect(svg?.getAttribute('width')).toBe('32') + }) + + it('should render in different contexts without errors', () => { + const { container } = render( +
+ +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + }) + + describe('CSS Variables', () => { + it('should use CSS custom properties for colors', () => { + const { container } = render() + const elementsWithCSSVars = container.querySelectorAll('[fill*="var("]') + + // All fill attributes should use CSS variables + expect(elementsWithCSSVars.length).toBeGreaterThan(0) + }) + + it('should have opacity attributes on quaternary elements', () => { + const { container } = render() + const quaternaryElements = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + quaternaryElements.forEach((element) => { + expect(element).toHaveAttribute('opacity', '0.18') + }) + }) + + it('should not have opacity on blue inverted elements', () => { + const { container } = render() + const blueElements = container.querySelectorAll('[fill="var(--color-saas-dify-blue-inverted)"]') + + blueElements.forEach((element) => { + expect(element).not.toHaveAttribute('opacity') + }) + }) + }) + + describe('SVG Structure', () => { + it('should have correct rect element structure', () => { + const { container } = render() + const rects = container.querySelectorAll('rect') + + // Each rect should have specific attributes + rects.forEach((rect) => { + expect(rect).toHaveAttribute('width', '2') + expect(rect).toHaveAttribute('height', '2') + expect(rect).toHaveAttribute('rx', '1') + expect(rect).toHaveAttribute('fill') + }) + }) + + it('should maintain exact pixel positioning', () => { + const { container } = render() + const rects = container.querySelectorAll('rect') + + // Ensure positioning attributes exist + rects.forEach((rect) => { + expect(rect).toHaveAttribute('x') + expect(rect).toHaveAttribute('y') + }) + }) + }) +}) diff --git a/web/app/components/billing/plan/assets/sandbox.spec.tsx b/web/app/components/billing/plan/assets/sandbox.spec.tsx new file mode 100644 index 0000000000..5a5accf362 --- /dev/null +++ b/web/app/components/billing/plan/assets/sandbox.spec.tsx @@ -0,0 +1,128 @@ +import { render } from '@testing-library/react' +import React from 'react' +import Sandbox from './sandbox' + +describe('Sandbox Icon Component', () => { + describe('Rendering', () => { + it('should render without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('should render an SVG element', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should have correct SVG attributes', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toHaveAttribute('xmlns', 'http://www.w3.org/2000/svg') + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + expect(svg).toHaveAttribute('viewBox', '0 0 32 32') + expect(svg).toHaveAttribute('fill', 'none') + }) + + it('should render correct number of SVG elements', () => { + const { container } = render() + const rects = container.querySelectorAll('rect') + const paths = container.querySelectorAll('path') + + // Based on the component structure + expect(rects.length).toBeGreaterThan(0) + expect(paths.length).toBeGreaterThan(0) + }) + + it('should render elements with correct fill colors', () => { + const { container } = render() + const blueElements = container.querySelectorAll('[fill="var(--color-saas-dify-blue-inverted)"]') + const quaternaryElements = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + expect(blueElements.length).toBeGreaterThan(0) + expect(quaternaryElements.length).toBeGreaterThan(0) + }) + }) + + describe('Component Behavior', () => { + it('should be memoized with React.memo', () => { + // React.memo wraps the component, so the display name should indicate memoization + // The component itself should be stable across re-renders + const { rerender, container } = render() + const firstRender = container.innerHTML + + rerender() + const secondRender = container.innerHTML + + expect(firstRender).toBe(secondRender) + }) + + it('should render consistently across multiple renders', () => { + const { container: container1 } = render() + const { container: container2 } = render() + + expect(container1.innerHTML).toBe(container2.innerHTML) + }) + }) + + describe('Accessibility', () => { + it('should render as a decorative image (no accessibility concerns for icon)', () => { + const { container } = render() + const svg = container.querySelector('svg') + + // SVG icons typically don't need aria-labels if they're decorative + // This test ensures the SVG is present and renderable + expect(svg).toBeInTheDocument() + }) + }) + + describe('Edge Cases', () => { + it('should handle multiple instances without conflicts', () => { + const { container } = render( + <> + + + + , + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(3) + }) + + it('should maintain structure when wrapped in other elements', () => { + const { container } = render( +
+ + + +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + expect(svg?.getAttribute('width')).toBe('32') + }) + }) + + describe('CSS Variables', () => { + it('should use CSS custom properties for colors', () => { + const { container } = render() + const elementsWithCSSVars = container.querySelectorAll('[fill*="var("]') + + // All fill attributes should use CSS variables + expect(elementsWithCSSVars.length).toBeGreaterThan(0) + }) + + it('should have opacity attributes on quaternary elements', () => { + const { container } = render() + const quaternaryElements = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + quaternaryElements.forEach((element) => { + expect(element).toHaveAttribute('opacity', '0.18') + }) + }) + }) +}) diff --git a/web/app/components/billing/plan/assets/team.spec.tsx b/web/app/components/billing/plan/assets/team.spec.tsx new file mode 100644 index 0000000000..60e69aa280 --- /dev/null +++ b/web/app/components/billing/plan/assets/team.spec.tsx @@ -0,0 +1,199 @@ +import { render } from '@testing-library/react' +import Team from './team' + +describe('Team Icon Component', () => { + describe('Rendering', () => { + it('should render without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('should render an SVG element', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should have correct SVG attributes', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toHaveAttribute('xmlns', 'http://www.w3.org/2000/svg') + expect(svg).toHaveAttribute('width', '32') + expect(svg).toHaveAttribute('height', '32') + expect(svg).toHaveAttribute('viewBox', '0 0 32 32') + expect(svg).toHaveAttribute('fill', 'none') + }) + + it('should render both rect and path elements', () => { + const { container } = render() + const rects = container.querySelectorAll('rect') + const paths = container.querySelectorAll('path') + + // Team icon uses both rects and paths + expect(rects.length).toBeGreaterThan(0) + expect(paths.length).toBeGreaterThan(0) + }) + + it('should render elements with correct fill colors', () => { + const { container } = render() + const blueElements = container.querySelectorAll('[fill="var(--color-saas-dify-blue-inverted)"]') + const quaternaryElements = container.querySelectorAll('[fill="var(--color-text-quaternary)"]') + + expect(blueElements.length).toBeGreaterThan(0) + expect(quaternaryElements.length).toBeGreaterThan(0) + }) + }) + + describe('Component Behavior', () => { + it('should render consistently across multiple renders', () => { + const { container: container1 } = render() + const { container: container2 } = render() + + expect(container1.innerHTML).toBe(container2.innerHTML) + }) + + it('should maintain stable output without memoization', () => { + const { container, rerender } = render() + const firstRender = container.innerHTML + + rerender() + const secondRender = container.innerHTML + + expect(firstRender).toBe(secondRender) + }) + }) + + describe('Accessibility', () => { + it('should render as a decorative image', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg).toBeInTheDocument() + }) + + it('should be usable in accessible contexts', () => { + const { container } = render( +
+ +
, + ) + + const wrapper = container.querySelector('[role="img"]') + expect(wrapper).toBeInTheDocument() + expect(wrapper).toHaveAttribute('aria-label', 'Team plan') + }) + }) + + describe('Edge Cases', () => { + it('should handle multiple instances without conflicts', () => { + const { container } = render( + <> + + + + , + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(3) + }) + + it('should maintain structure when wrapped in other elements', () => { + const { container } = render( +
+ + + +
, + ) + + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + expect(svg?.getAttribute('width')).toBe('32') + }) + + it('should render correctly in list context', () => { + const { container } = render( +
    +
  • + +
  • +
  • + +
  • +
, + ) + + const svgs = container.querySelectorAll('svg') + expect(svgs).toHaveLength(2) + }) + }) + + describe('CSS Variables', () => { + it('should use CSS custom properties for colors', () => { + const { container } = render() + const elementsWithCSSVars = container.querySelectorAll('[fill*="var("]') + + expect(elementsWithCSSVars.length).toBeGreaterThan(0) + }) + + it('should have opacity attributes on quaternary path elements', () => { + const { container } = render() + const quaternaryPaths = container.querySelectorAll('path[fill="var(--color-text-quaternary)"]') + + quaternaryPaths.forEach((path) => { + expect(path).toHaveAttribute('opacity', '0.18') + }) + }) + + it('should not have opacity on blue inverted elements', () => { + const { container } = render() + const blueRects = container.querySelectorAll('rect[fill="var(--color-saas-dify-blue-inverted)"]') + + blueRects.forEach((rect) => { + expect(rect).not.toHaveAttribute('opacity') + }) + }) + }) + + describe('SVG Structure', () => { + it('should have correct rect element attributes', () => { + const { container } = render() + const rects = container.querySelectorAll('rect') + + rects.forEach((rect) => { + expect(rect).toHaveAttribute('x') + expect(rect).toHaveAttribute('y') + expect(rect).toHaveAttribute('width', '2') + expect(rect).toHaveAttribute('height', '2') + expect(rect).toHaveAttribute('rx', '1') + expect(rect).toHaveAttribute('fill') + }) + }) + + it('should have correct path element structure', () => { + const { container } = render() + const paths = container.querySelectorAll('path') + + paths.forEach((path) => { + expect(path).toHaveAttribute('d') + expect(path).toHaveAttribute('fill') + }) + }) + + it('should maintain proper element positioning', () => { + const { container } = render() + const svg = container.querySelector('svg') + + expect(svg?.childNodes.length).toBeGreaterThan(0) + }) + }) + + describe('Export', () => { + it('should be the default export', () => { + expect(Team).toBeDefined() + expect(typeof Team).toBe('function') + }) + }) +})