diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/TsxCollectorTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/TsxCollectorTest.kt new file mode 100644 index 0000000000..080e921cef --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/TsxCollectorTest.kt @@ -0,0 +1,681 @@ +package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors + +import de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes.AvailableFileMetrics +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.treesitter.TSParser +import org.treesitter.TreeSitterTypescript +import java.io.File + +class TsxCollectorTest { + private var parser = TSParser() + private val collector = TypescriptCollector() + + @BeforeEach + fun setUp() { + parser.setLanguage(TreeSitterTypescript()) + } + + private fun createTestFile(content: String): File { + val tempFile = File.createTempFile("testFile", ".tsx") + tempFile.writeText(content) + tempFile.deleteOnExit() + return tempFile + } + + @Test + fun `should count functional component with JSX for number of functions`() { + // Arrange + val fileContent = """ + import React from 'react'; + + const MyComponent: React.FC = () => { + return
Hello World
; + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.NUMBER_OF_FUNCTIONS.metricName]).isEqualTo(1.0) + } + + @Test + fun `should count function component with typed props for number of functions`() { + // Arrange + val fileContent = """ + interface Props { + name: string; + age: number; + } + + function UserCard({ name, age }: Props) { + return ( +
+

{name}

+

Age: {age}

+
+ ); + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.NUMBER_OF_FUNCTIONS.metricName]).isEqualTo(1.0) + } + + @Test + fun `should count class component with render method for number of functions`() { + // Arrange + val fileContent = """ + import React, { Component } from 'react'; + + interface State { + count: number; + } + + class Counter extends Component<{}, State> { + constructor(props: {}) { + super(props); + this.state = { count: 0 }; + } + + increment() { + this.setState({ count: this.state.count + 1 }); + } + + render() { + return ( +
+

Count: {this.state.count}

+ +
+ ); + } + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.NUMBER_OF_FUNCTIONS.metricName]).isEqualTo(3.0) + } + + @Test + fun `should count complexity with JSX ternary operators`() { + // Arrange + val fileContent = """ + const ConditionalComponent: React.FC<{ isLoading: boolean }> = ({ isLoading }) => { + return ( +
+ {isLoading ? : } +
+ ); + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.COMPLEXITY.metricName]).isEqualTo(2.0) //TODO not counting ternary in div + } + + @Test + fun `should count complexity with JSX logical AND operators`() { + // Arrange + val fileContent = """ + const OptionalComponent = ({ showMessage }: { showMessage: boolean }) => { + return ( +
+ {showMessage &&

Message is visible

} + {!showMessage &&

Message is hidden

} +
+ ); + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.COMPLEXITY.metricName]).isEqualTo(3.0) //TODO: correctly counts && even within errors + } + + @Test + fun `should count complexity with multiple conditional renders`() { + // Arrange + val fileContent = """ + const StatusDisplay = ({ status }: { status: string }) => { + if (status === 'loading') { + return ; + } else if (status === 'error') { + return ; + } else if (status === 'success') { + return ; + } + return ; + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.COMPLEXITY.metricName]).isEqualTo(4.0) + } + + @Test + fun `should count complexity with switch statement for JSX rendering`() { + // Arrange + val fileContent = """ + const SwitchComponent = ({ type }: { type: string }) => { + switch (type) { + case 'primary': + return ; + case 'secondary': + return ; + case 'danger': + return ; + default: + return ; + } + } + """.trimIndent() + val input = createTestFile(fileContent) + + // Act + val result = collector.collectMetricsForFile(input) + + // Assert + Assertions.assertThat(result.attributes[AvailableFileMetrics.COMPLEXITY.metricName]).isEqualTo(5.0) //TODO: parsing of switch statements is incorrect after first < + } + + @Test + fun `should count JSX with fragments and multiple children`() { + // Arrange + val fileContent = """ + const FragmentComponent = () => { + return ( + <> +
+
+ +
+