Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/3879/map order #3905

Merged
merged 12 commits into from
Feb 3, 2025
1 change: 1 addition & 0 deletions visualization/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
### Added

- Add a cross-hair when hovering over the color quantile diagram [#3827](https://github.com/MaibornWolff/codecharta/pull/3827)
- Maps are always shown and rendered in alphabetical order [#3905](https://github.com/MaibornWolff/codecharta/pull/3905)

### Changed

Expand Down
2 changes: 1 addition & 1 deletion visualization/app/codeCharta/e2e/url.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("codecharta", () => {
it("should load data when file parameters in url are valid", async () => {
await mockResponses()
await goto(`${CC_URL}?file=fileOne.json&file=fileTwo.json`)
await checkAllFileNames(["Sample Project with Edges", "Sample Project"])
await checkAllFileNames(["Sample Project", "Sample Project with Edges"])
})

it("should throw errors when file parameters in url are invalid and load sample data instead", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,14 @@ describe("loadFileService", () => {

expect(storeDispatchSpy).toHaveBeenCalledWith(setCurrentFilesAreSampleFiles({ value: false }))
expect(CCFilesUnderTest.length).toEqual(3)
expect(CCFilesUnderTest[0].fileMeta.fileName).toEqual("SecondFile")
expect(CCFilesUnderTest[0].fileMeta.fileChecksum).toEqual("hash_1")
expect(CCFilesUnderTest[1].fileMeta.fileName).toEqual("ThirdFile")
expect(CCFilesUnderTest[1].fileMeta.fileChecksum).toEqual("hash_2_1")
expect(CCFilesUnderTest[2].fileMeta.fileName).toEqual("FourthFile")
expect(CCFilesUnderTest[2].fileMeta.fileChecksum).toEqual("hash_3")
const allFileNames = CCFilesUnderTest.map(file => file.fileMeta.fileName)
expect(allFileNames).toContain("SecondFile")
expect(allFileNames).toContain("ThirdFile")
expect(allFileNames).toContain("FourthFile")
const allChecksums = CCFilesUnderTest.map(file => file.fileMeta.fileChecksum)
expect(allChecksums).toContain("hash_1")
expect(allChecksums).toContain("hash_2_1")
expect(allChecksums).toContain("hash_3")
})

it("should keep sample files when loading the same sample file and after that another different file", () => {
Expand Down Expand Up @@ -352,7 +354,7 @@ describe("loadFileService", () => {

try {
codeChartaService.loadFiles([{ fileName: "DifferentName", content: validFileContent, fileSize: 42 }])
} catch (e) {}
} catch (_) {}

const CCFilesUnderTest = getCCFiles(state.getValue().files)

Expand All @@ -370,7 +372,7 @@ describe("loadFileService", () => {

try {
codeChartaService.loadFiles([{ fileName: "FirstFile", content: validFileContent, fileSize: 42 }])
} catch (e) {}
} catch (_) {}

const filesUnderTest: FileState[] = state.getValue().files

Expand Down Expand Up @@ -602,8 +604,7 @@ describe("loadFileService", () => {
})

it("should load files ignoring the authors attribute", () => {
const testFileContentWithAuthors = TEST_FILE_CONTENT_WITH_AUTHORS
const testJsonWithAuthors = JSON.stringify(testFileContentWithAuthors)
const testJsonWithAuthors = JSON.stringify(TEST_FILE_CONTENT_WITH_AUTHORS)
const expectedFileContentWithoutAuthors = TEST_FILE_CONTENT_WITHOUT_AUTHORS
const ccFile = getCCFileAndDecorateFileChecksum(testJsonWithAuthors)

Expand All @@ -613,8 +614,7 @@ describe("loadFileService", () => {
})

it("should show warnings for files containing the authors attribute", () => {
const testFileContentWithAuthors = TEST_FILE_CONTENT_WITH_AUTHORS
const testJsonWithAuthors = JSON.stringify(testFileContentWithAuthors)
const testJsonWithAuthors = JSON.stringify(TEST_FILE_CONTENT_WITH_AUTHORS)
const expectedFileValidationResult: CCFileValidationResult[] = [
{
fileName: "FirstFile",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
setStandardByNames,
switchReferenceAndComparison
} from "./files.actions"
import { TEST_DELTA_MAP_A, TEST_DELTA_MAP_B } from "../../../util/dataMocks"
import { TEST_DELTA_MAP_A, TEST_DELTA_MAP_B, TEST_FILE_DATA } from "../../../util/dataMocks"
import { isDeltaState, isPartialState } from "../../../model/files/files.helper"
import { FileSelectionState, FileState } from "../../../model/files/files"
import { clone } from "../../../util/clone"
Expand All @@ -31,6 +31,65 @@ describe("files", () => {

expect(result).toEqual(newFiles)
})

it("should set new files and sort them in descending order by name and checksum", () => {
const newFiles: FileState[] = [
{
file: { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "B", fileChecksum: "1" } },
selectedAs: FileSelectionState.Partial
},
{
file: { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "A", fileChecksum: "2" } },
selectedAs: FileSelectionState.Partial
},
{
file: { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "C", fileChecksum: "3" } },
selectedAs: FileSelectionState.Partial
},
{
file: { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "B", fileChecksum: "4" } },
selectedAs: FileSelectionState.Partial
}
]

const result = files(state, setFiles({ value: newFiles }))

expect(result.length).toBe(4)
expect(result[0].file.fileMeta.fileName).toBe("A")
expect(result[0].file.fileMeta.fileChecksum).toBe("2")
expect(result[1].file.fileMeta.fileName).toBe("B")
expect(result[1].file.fileMeta.fileChecksum).toBe("1")
expect(result[2].file.fileMeta.fileName).toBe("B")
expect(result[2].file.fileMeta.fileChecksum).toBe("4")
expect(result[3].file.fileMeta.fileName).toBe("C")
expect(result[3].file.fileMeta.fileChecksum).toBe("3")
})
})

describe("Action: ADD_FILE", () => {
it("should add a file and sort the files in descending order by name and checksum", () => {
const newFile1 = { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "B", fileChecksum: "1" } }
const newFile2 = { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "A", fileChecksum: "2" } }
const newFile3 = { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "C", fileChecksum: "3" } }
const newFile4 = { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "B", fileChecksum: "4" } }

state = files(state, addFile({ file: newFile1 }))
state = files(state, addFile({ file: newFile2 }))
state = files(state, addFile({ file: newFile3 }))
const result = files(state, addFile({ file: newFile4 }))

expect(result.length).toBe(6)
expect(result[0].file.fileMeta.fileName).toBe("A")
expect(result[0].file.fileMeta.fileChecksum).toBe("2")
expect(result[1].file.fileMeta.fileName).toBe("B")
expect(result[1].file.fileMeta.fileChecksum).toBe("1")
expect(result[2].file.fileMeta.fileName).toBe("B")
expect(result[2].file.fileMeta.fileChecksum).toBe("4")
expect(result[3].file.fileMeta.fileName).toBe("C")
expect(result[3].file.fileMeta.fileChecksum).toBe("3")
expect(result[4].file.fileMeta.fileName).toBe("fileA")
expect(result[5].file.fileMeta.fileName).toBe("fileB")
})
})

describe("Action: SET_DELTA", () => {
Expand Down
31 changes: 28 additions & 3 deletions visualization/app/codeCharta/state/store/files/files.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import { CCFile } from "../../../codeCharta.model"
import { FileSelectionState, FileState } from "../../../model/files/files"
import { isEqual } from "../../../model/files/files.helper"
import { createReducer, on } from "@ngrx/store"
import { setState } from "../util/setState.reducer.factory"

export const defaultFiles: FileState[] = []
export const files = createReducer(
defaultFiles,
on(setFiles, setState(defaultFiles)),
on(addFile, (state, action) => [...state, { file: action.file, selectedAs: FileSelectionState.None }]),
on(setFiles, (state, action) => setFileStatesState(state, action.value)),
on(addFile, (state, action) => insertIntoSortedState(state, action.file)),
on(removeFiles, (state, action) => removeFilesFromState(state, action.fileNames)),
on(setDelta, (state, action) => setDeltaState(state, action.referenceFile, action.comparisonFile)),
on(setDeltaReference, (state, action) => setDeltaReferenceState(state, action.file)),
Expand All @@ -34,6 +33,32 @@ export const files = createReducer(
on(setStandardByNames, (state, action) => setStandardByNamesState(state, action.fileNames))
)

function setFileStatesState(_: FileState[], fileStates: FileState[]): FileState[] {
if (fileStates === undefined) {
return defaultFiles
}
return [...fileStates].sort((fileStateA, fileStateB) => (greaterEqualThan(fileStateA.file, fileStateB.file) ? 1 : -1))
}

function insertIntoSortedState(state: FileState[], file: CCFile) {
const index = state.findIndex(fileState => greaterEqualThan(fileState.file, file))
const fileState = { file, selectedAs: FileSelectionState.None }
if (index === -1) {
return [...state, fileState]
}
return [...state.slice(0, index), fileState, ...state.slice(index)]
}

function greaterEqualThan(fileA: CCFile, fileB: CCFile): boolean {
if (fileA.fileMeta.fileName > fileB.fileMeta.fileName) {
return true
}
if (fileA.fileMeta.fileName < fileB.fileMeta.fileName) {
return false
}
return fileA.fileMeta.fileChecksum >= fileB.fileMeta.fileChecksum
}

function removeFilesFromState(state: FileState[], fileNames: string[]): FileState[] {
if (fileNames.length === 0) {
return state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ describe("FileSelectionModeService", () => {
})
store = TestBed.inject(Store)
state = TestBed.inject(State)
store.dispatch(addFile({ file: TEST_FILE_DATA_JAVA })) //fileName: "fileB"
store.dispatch(addFile({ file: TEST_FILE_DATA }))
store.dispatch(addFile({ file: TEST_FILE_DATA_JAVA }))
store.dispatch(setStandard({ files: [TEST_FILE_DATA] }))
store.dispatch(setStandard({ files: [TEST_FILE_DATA] })) //fileName: "fileA"

fileSelectionModeService = new FileSelectionModeService(store, state)
})
Expand All @@ -38,8 +38,8 @@ describe("FileSelectionModeService", () => {

fileSelectionModeService.toggle()
store.dispatch(setDelta({ referenceFile: TEST_FILE_DATA_JAVA, comparisonFile: TEST_FILE_DATA }))

fileSelectionModeService.toggle()

fileStates = state.getValue().files
expect(fileStates[0].selectedAs).toBe(FileSelectionState.Partial)
expect(fileStates[1].selectedAs).toBe(FileSelectionState.None)
Expand All @@ -53,4 +53,26 @@ describe("FileSelectionModeService", () => {
fileSelectionModeService.toggle()
expect(referenceFileSelector(state.getValue())).toBe(TEST_FILE_DATA)
})

it("should remain sorted after toggling", () => {
const file1 = { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "bFile", fileChecksum: "2" } }
const file2 = { ...TEST_FILE_DATA_JAVA, fileMeta: { ...TEST_FILE_DATA_JAVA.fileMeta, fileName: "aFile", fileChecksum: "1" } }
const file3 = { ...TEST_FILE_DATA, fileMeta: { ...TEST_FILE_DATA.fileMeta, fileName: "aFile", fileChecksum: "2" } }

store.dispatch(addFile({ file: file1 }))
store.dispatch(addFile({ file: file2 }))
store.dispatch(addFile({ file: file3 }))

fileSelectionModeService.toggle()
fileSelectionModeService.toggle()

const fileStates = state.getValue().files
expect(fileStates[0].file.fileMeta.fileName).toBe("aFile")
expect(fileStates[0].file.fileMeta.fileChecksum).toBe("1")
expect(fileStates[1].file.fileMeta.fileName).toBe("aFile")
expect(fileStates[1].file.fileMeta.fileChecksum).toBe("2")
expect(fileStates[2].file.fileMeta.fileName).toBe("bFile")
expect(fileStates[3].file.fileMeta.fileName).toBe("fileA")
expect(fileStates[4].file.fileMeta.fileName).toBe("fileB")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe("UploadFilesService", () => {
mockFileInput = {
files: [new File([stringify(TEST_FILE_CONTENT)], "test.cc.json", { type: "application/json" })],
click: jest.fn(),
addEventListener: jest.fn((event, callback) => {})
addEventListener: jest.fn((_event, _callback) => {})
} as unknown as HTMLInputElement
;(createCCFileInput as jest.Mock).mockReturnValue(mockFileInput)
})
Expand Down