Skip to content

Commit

Permalink
Fix/3767/legend panel (#3770)
Browse files Browse the repository at this point in the history
* fix(visualization): close legend panel on outside click

* begin test(visualization): test closing on outside clicks for legend panel

* chore(visualization): update changelog

* chore(visualization): update changelog

* test(visualization): only call render once for instantiating the tests and improve tests

* test(visualization): add test for click on pop up and switch to userEvents

* chore(visualization): formatting and rearranging

* test(visualization): formatting and fixing tests

* chore(visualization):update changelog

* test(visualization):fix tests and open legend panel after elements were clicked

---------

Co-authored-by: lisa.walter <[email protected]>
  • Loading branch information
polina-schoenfeld-mw and lisa.walter authored Sep 26, 2024
1 parent 10c81b8 commit c089981
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 34 deletions.
10 changes: 8 additions & 2 deletions visualization/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/)

## [unreleased] (Added 🚀 | Changed | Removed 🗑 | Fixed 🐞 | Chore 👨‍💻 👩‍💻)

### Added 🚀

- Add thousands separation for big numbers [#3758](https://github.com/MaibornWolff/codecharta/pull/3758)

### Fixed 🐞

- Remove horizontal scrollbar in the File/ Node Explorer dropdown and styling fixes [#3765](https://github.com/MaibornWolff/codecharta/pull/3765)
- Fix issue when multiple files are selected then the edge metrics in the primary metrics section are not displayed correctly. [#3753](https://github.com/MaibornWolff/codecharta/pull/3753)
- Metric Scenarios (before Metric Templates) are available again [#3762](https://github.com/MaibornWolff/codecharta/pull/3762)
- Legend Panel closes on outside click [#3770](https://github.com/MaibornWolff/codecharta/pull/3770)

### Changed

- Add thousands seperation for metrics value [#3758](https://github.com/MaibornWolff/codecharta/pull/3758)
- Renaming of Metric Templates to Metric Scenarios [#3656](https://github.com/MaibornWolff/codecharta/pull/3656)

## [1.128.0] - 2024-09-18

Expand All @@ -27,7 +34,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
- Fix issue where zooming out too much makes the map disappear and zooming in too much causes you to go through the map. [#3697](https://github.com/MaibornWolff/codecharta/pull/3697)
- Camera perspective is correctly adopted from the custom configuration[#3698](https://github.com/MaibornWolff/codecharta/pull/3698)
- Fix camera behavior when `Reset Camera when changing map` option is deactivated [#3699](https://github.com/MaibornWolff/codecharta/pull/3699)
- Fix issue when multiple files are selected then the edge metrics in the primary metrics section are not displayed correctly. [#3753](https://github.com/MaibornWolff/codecharta/pull/3753)

### Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TestBed } from "@angular/core/testing"
import { fireEvent, render, screen } from "@testing-library/angular"
import { render, screen, waitFor } from "@testing-library/angular"
import { expect } from "@jest/globals"
import { isDeltaStateSelector } from "../../state/selectors/isDeltaState.selector"
import { LegendPanelComponent } from "./legendPanel.component"
Expand All @@ -15,41 +15,46 @@ import { mapColorsSelector } from "../../state/store/appSettings/mapColors/mapCo
import { defaultMapColors } from "../../state/store/appSettings/mapColors/mapColors.reducer"
import { selectedColorMetricDataSelector } from "../../state/selectors/accumulatedData/metricData/selectedColorMetricData.selector"
import { attributeDescriptorsSelector } from "../../state/store/fileSettings/attributeDescriptors/attributeDescriptors.selector"
import { ViewContainerRef } from "@angular/core"
import userEvent from "@testing-library/user-event"
import { edgeMetricSelector } from "../../state/store/dynamicSettings/edgeMetric/edgeMetric.selector"
import { legendMarkedPackagesSelector } from "./legendMarkedPackages/legendMarkedPackages.selector"

describe("LegendPanelController", () => {
const selectors = [
{ selector: heightMetricSelector, value: "sonar_complexity" },
{ selector: areaMetricSelector, value: "loc" },
{ selector: colorMetricSelector, value: "rloc" },
{ selector: edgeMetricSelector, value: "rloc" },
{ selector: colorRangeSelector, value: { from: 21, to: 42, max: 9001 } },
{ selector: isDeltaStateSelector, value: true },
{ selector: mapColorsSelector, value: defaultMapColors },
{ selector: selectedColorMetricDataSelector, value: {} },
{ selector: attributeDescriptorsSelector, value: {} },
{ selector: legendMarkedPackagesSelector, value: {} }
]

describe(LegendPanelComponent.name, () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [LegendPanelModule],
providers: [
provideMockStore({
selectors: [
{ selector: heightMetricSelector, value: "sonar_complexity" },
{ selector: areaMetricSelector, value: "loc" },
{ selector: colorMetricSelector, value: "rloc" },
{ selector: colorRangeSelector, value: { from: 21, to: 42, max: 9001 } },
{ selector: isDeltaStateSelector, value: true },
{ selector: mapColorsSelector, value: defaultMapColors },
{ selector: selectedColorMetricDataSelector, value: {} },
{ selector: attributeDescriptorsSelector, value: {} }
]
}),
{ provide: State, useValue: {} }
]
providers: [provideMockStore({ selectors }), { provide: State, useValue: {} }, ViewContainerRef]
})
})

it("should open and close", async () => {
const { container } = await render(LegendPanelComponent, { excludeComponentDeclaration: true })
const { container, fixture } = await render(LegendPanelComponent, { excludeComponentDeclaration: true })

expect(isLegendPanelOpen(container)).toBe(false)

const openLegendButton = screen.getByTitle("Show panel")
fireEvent.click(openLegendButton)
expect(isLegendPanelOpen(container)).toBe(true)
await userEvent.click(openLegendButton)
fixture.detectChanges()
await waitFor(() => expect(isLegendPanelOpen(container)).toBe(true))

const closeLegendButton = screen.getByTitle("Hide panel")
fireEvent.click(closeLegendButton)
expect(isLegendPanelOpen(container)).toBe(false)
await userEvent.click(closeLegendButton)
fixture.detectChanges()
await waitFor(() => expect(isLegendPanelOpen(container)).toBe(false))
})

it("should display legend for single mode", async () => {
Expand All @@ -58,14 +63,15 @@ describe("LegendPanelController", () => {
store.overrideSelector(isDeltaStateSelector, false)
store.refreshState()
detectChanges()
fireEvent.click(screen.getByTitle("Show panel"))
await userEvent.click(screen.getByTitle("Show panel"))

expect(screen.queryAllByText("delta", { exact: false }).length).not.toBeGreaterThan(0)

const metricDescriptions = container.querySelectorAll("cc-legend-block")
expect(metricDescriptions[0].textContent).toMatch("Area metric: Lines of Code (loc)")
expect(metricDescriptions[1].textContent).toMatch("Height metric: Cyclomatic Complexity (sonar_complexity)")
expect(metricDescriptions[2].textContent).toMatch("Color metric: Real Lines of Code (rloc)")
expect(metricDescriptions[2].textContent).toMatch("Edge metric: Real Lines of Code (rloc)")
expect(metricDescriptions[3].textContent).toMatch("Color metric: Real Lines of Code (rloc)")
})

it("should contain elements with titles and links if attributeDescriptors are present", async () => {
Expand All @@ -88,7 +94,7 @@ describe("LegendPanelController", () => {
})
store.refreshState()
detectChanges()
fireEvent.click(screen.getByTitle("Show panel"))
await userEvent.click(screen.getByTitle("Show panel"))
expect(screen.queryAllByText("delta", { exact: false }).length).not.toBeGreaterThan(0)

const metricDescriptions = container.querySelectorAll("cc-legend-block")
Expand All @@ -98,14 +104,17 @@ describe("LegendPanelController", () => {
"COMPLEXITY_Title (sonar_complexity):\nCOMPLEXITY_description\nLow Values: COMPLEXITY_lowValue"
)
expect(metricDescriptions[1].querySelector("a")).toBeNull()
expect(metricDescriptions[2].textContent).toMatch("Color metric: RLOC_Title (rloc)")
expect(metricDescriptions[2].textContent).toMatch("Edge metric: RLOC_Title (rloc)")
expect(metricDescriptions[2].firstElementChild.getAttribute("title")).toMatch("RLOC_Title (rloc)")
expect(metricDescriptions[2].querySelector("a").getAttribute("href")).toMatch(metricLink)
expect(metricDescriptions[3].textContent).toMatch("Color metric: RLOC_Title (rloc)")
expect(metricDescriptions[3].firstElementChild.getAttribute("title")).toMatch("RLOC_Title (rloc)")
expect(metricDescriptions[3].querySelector("a").getAttribute("href")).toMatch(metricLink)
})

it("should display legend for delta mode", async () => {
const { container } = await render(LegendPanelComponent, { excludeComponentDeclaration: true })
fireEvent.click(screen.getByTitle("Show panel"))
await userEvent.click(screen.getByTitle("Show panel"))

expect(screen.queryAllByText("delta", { exact: false }).length).toBeGreaterThan(0)

Expand All @@ -121,8 +130,56 @@ describe("LegendPanelController", () => {
const openingButton = container.querySelector(".panel-button")
expect(openingButton.classList).toContain("isAttributeSideBarVisible")
})

describe("closing on outside clicks", () => {
it("should subscribe to mousedown events when opening", async () => {
const addEventListenerSpy = jest.spyOn(document, "addEventListener")
const { fixture } = await render(LegendPanelComponent, { excludeComponentDeclaration: true })
fixture.componentInstance.ngOnInit()
expect(addEventListenerSpy).toHaveBeenCalledWith("mousedown", expect.any(Function))
})

it("should unsubscribe mousedown events when destroyed", async () => {
const removeEventListenerSpy = jest.spyOn(document, "removeEventListener")
const { fixture } = await render(LegendPanelComponent, { excludeComponentDeclaration: true })
fixture.componentInstance.ngOnInit()
fixture.componentInstance.ngOnDestroy()
expect(removeEventListenerSpy).toHaveBeenCalledWith("mousedown", expect.any(Function))
})

it("should close on outside clicks", async () => {
const { container, fixture } = await render(LegendPanelComponent, { excludeComponentDeclaration: true })
expect(isLegendPanelOpen(container)).toBe(false)

const openLegendButton = screen.getByTitle("Show panel")
await userEvent.click(openLegendButton)
fixture.detectChanges()
expect(isLegendPanelOpen(container)).toBe(true)

await userEvent.click(document.body)
fixture.detectChanges()
expect(isLegendPanelOpen(container)).toBe(false)
})

it("should not close when clicking inside", async () => {
const { container, fixture } = await render(`<cc-legend-panel></cc-legend-panel>`, {
excludeComponentDeclaration: true
})
const panel = container.querySelector("cc-legend-panel")
expect(isLegendPanelOpen(container)).toBe(false)

const openLegendButton = screen.getByTitle("Show panel")
await userEvent.click(openLegendButton)
fixture.detectChanges()
expect(isLegendPanelOpen(container)).toBe(true)

await userEvent.click(panel)
fixture.detectChanges()
expect(isLegendPanelOpen(container)).toBe(true)
})
})
})

function isLegendPanelOpen(container: Element) {
return container.querySelector(".block-wrapper").classList.contains("visible")
return container.querySelector("#legend-panel").classList.contains("visible")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,54 @@ import { heightMetricSelector } from "../../state/store/dynamicSettings/heightMe
import { areaMetricSelector } from "../../state/store/dynamicSettings/areaMetric/areaMetric.selector"
import { colorMetricSelector } from "../../state/store/dynamicSettings/colorMetric/colorMetric.selector"
import { edgeMetricSelector } from "../../state/store/dynamicSettings/edgeMetric/edgeMetric.selector"
import { Component } from "@angular/core"
import { Component, ViewContainerRef, OnDestroy, OnInit } from "@angular/core"

@Component({
selector: "cc-legend-panel",
templateUrl: "./legendPanel.component.html",
styleUrls: ["./legendPanel.component.scss"]
})
export class LegendPanelComponent {
export class LegendPanelComponent implements OnInit, OnDestroy {
isLegendVisible = false
isDeltaState$ = this.store.select(isDeltaStateSelector)
heightMetric$ = this.store.select(heightMetricSelector)
areaMetric$ = this.store.select(areaMetricSelector)
colorMetric$ = this.store.select(colorMetricSelector)
edgeMetric$ = this.store.select(edgeMetricSelector)

private mouseDownListener?: (event: MouseEvent) => void

constructor(
private store: Store<CcState>,
public isAttributeSideBarVisibleService: IsAttributeSideBarVisibleService
public isAttributeSideBarVisibleService: IsAttributeSideBarVisibleService,
private readonly viewReference: ViewContainerRef
) {}

ngOnInit(): void {
this.mouseDownListener = (event: MouseEvent) => this.collapseOnOutsideClick(event)
document.addEventListener("mousedown", this.mouseDownListener)
}

ngOnDestroy(): void {
if (this.mouseDownListener) {
document.removeEventListener("mousedown", this.mouseDownListener)
}
}

toggleIsLegendVisible() {
this.isLegendVisible = !this.isLegendVisible
}

private collapseOnOutsideClick(event: MouseEvent) {
const target = event.target as Node
if (this.isLegendVisible) {
const clickedInside = this.viewReference.element.nativeElement.contains(target)
const overlayPaneElement = document.querySelector(".cdk-overlay-container")
const clickedWithinOverlayPane = overlayPaneElement ? overlayPaneElement.contains(target) : false

if (!clickedInside && !clickedWithinOverlayPane) {
this.isLegendVisible = false
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ describe("LegendPanel", () => {

async function setupTest() {
await uploadFilesButton.openFiles(["./app/codeCharta/resources/sample1_with_different_edges.cc.json"])
await legendPanelObject.open()
await searchPanel.toggle()
await mapTreeViewLevel.openContextMenu("/root")
await clickButtonOnPageElement(".colorButton:nth-child(2)")
await legendPanelObject.open()
}

it("should highlight a folder and add to legend", async () => {
Expand Down Expand Up @@ -58,6 +58,7 @@ describe("LegendPanel", () => {
await clickButtonOnPageElement(".colorButton:nth-child(1)")
await mapTreeViewLevel.openContextMenu("/root")
await clickButtonOnPageElement(".colorButton:nth-child(2)")
await legendPanelObject.open()
const selectedFolder = await legendPanelObject.getFilename()
expect(selectedFolder).toMatch(/root\/ParentLeaf\s*/)
})
Expand Down

0 comments on commit c089981

Please sign in to comment.