diff --git a/src/app/services/download-file.service.spec.ts b/src/app/services/download-file.service.spec.ts new file mode 100644 index 000000000..c96cd9926 --- /dev/null +++ b/src/app/services/download-file.service.spec.ts @@ -0,0 +1,40 @@ +import { FileFormat } from '@app/types/file.type'; +import DownloadService from './download-file.service'; + +describe('download-file function', () => { + const service = new DownloadService(); + const anchor = { + href: '', + download: '', + click: jest.fn() + }; + + const mockId = 1234; + const mockDate = '2024 01 01'; + + global.document.createElement = jest.fn().mockReturnValue(anchor); + global.URL.createObjectURL = jest.fn(); + + global.Date.prototype.getTime = jest.fn(() => mockId); + global.Date.prototype.toISOString = jest.fn(() => mockDate); + + const name = 'settings'; + const format: FileFormat = 'csv'; + + const data = JSON.stringify([1, 2, 3]); + + it('should download the settings', () => { + service.downloadFile(data, name, format); + expect(anchor.download).toEqual(`${mockDate}-${mockId}-${name}.${format}`); + }); + + it('should download the settings with default configuration', () => { + service.downloadFile(data); + expect(anchor.download).toContain(`${mockDate}-${mockId}.json`); + }); + + it('should click the anchor', () => { + service.downloadFile(data); + expect(anchor.click).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/src/app/services/download-file.service.ts b/src/app/services/download-file.service.ts new file mode 100644 index 000000000..2efbe84ab --- /dev/null +++ b/src/app/services/download-file.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { FileFormat } from '@app/types/file.type'; + +@Injectable() +export default class DownloadService { + /** + * Download data into a json or csv file. The file name will be as following: "date-id-name.format". + * the Id is the current time in seconds. + */ + downloadFile(data: BlobPart, name?: string, format?: FileFormat) { + const blob = new Blob([data], { type: `application/${format ?? 'json'}` }); + const url = URL.createObjectURL(blob); + const date = new Date().toISOString().slice(0, 10); + const id = new Date().getTime(); + + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = `${date}-${id}${name ? '-' + name : ''}.${format ?? 'json'}`; + anchor.click(); + } +} \ No newline at end of file diff --git a/src/app/settings/index.component.spec.ts b/src/app/settings/index.component.spec.ts index bcb4cb020..d6af6b5a9 100644 --- a/src/app/settings/index.component.spec.ts +++ b/src/app/settings/index.component.spec.ts @@ -5,6 +5,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatDialog } from '@angular/material/dialog'; import { Key } from '@app/types/config'; import { Sidebar, SidebarItem } from '@app/types/navigation'; +import DownloadService from '@services/download-file.service'; import { IconsService } from '@services/icons.service'; import { NavigationService } from '@services/navigation.service'; import { NotificationService } from '@services/notification.service'; @@ -127,6 +128,10 @@ describe('IndexComponent', () => { get: jest.fn() }; + const mockDownloadService = { + downloadFile: jest.fn() + }; + beforeEach(() => { component = TestBed.configureTestingModule({ providers: [ @@ -137,6 +142,7 @@ describe('IndexComponent', () => { { provide: StorageService, useValue: mockStorageService }, { provide: MatDialog, useValue: mockDialog }, { provide: HttpClient, useValue: mockHttpClient }, + { provide: DownloadService, useValue: mockDownloadService }, ] }).inject(IndexComponent); component.ngOnInit(); @@ -306,27 +312,13 @@ describe('IndexComponent', () => { }); }); - describe('exportData', () => { - const anchor = { - href: '', - download: '', - click: jest.fn() - }; - - global.document.createElement = jest.fn().mockReturnValue(anchor); - - global.URL.createObjectURL = jest.fn(); - + describe('exportData', () => { beforeEach(() => { component.exportData(); }); - it('should download the settings', () => { - expect(anchor.download).toContain('settings.json'); - }); - - it('should click the anchor', () => { - expect(anchor.click).toHaveBeenCalled(); + it('should call downloadFile', () => { + expect(mockDownloadService.downloadFile).toHaveBeenCalled(); }); it('should notify on success', () => { diff --git a/src/app/settings/index.component.ts b/src/app/settings/index.component.ts index 1b5ef2222..c87d44541 100644 --- a/src/app/settings/index.component.ts +++ b/src/app/settings/index.component.ts @@ -15,6 +15,7 @@ import { Sidebar, SidebarItem } from '@app/types/navigation'; import { PageHeaderComponent } from '@components/page-header.component'; import { PageSectionHeaderComponent } from '@components/page-section-header.component'; import { PageSectionComponent } from '@components/page-section.component'; +import DownloadService from '@services/download-file.service'; import { IconsService } from '@services/icons.service'; import { NavigationService } from '@services/navigation.service'; import { NotificationService } from '@services/notification.service'; @@ -129,6 +130,7 @@ main { providers: [ QueryParamsService, NotificationService, + DownloadService, ], imports: [ PageHeaderComponent, @@ -159,6 +161,7 @@ export class IndexComponent implements OnInit { private readonly navigationService = inject(NavigationService); private readonly storageService = inject(StorageService); private readonly httpClient = inject(HttpClient); + private readonly downloadService = inject(DownloadService); ngOnInit(): void { this.keys = this.sortKeys(this.storageService.restoreKeys()); @@ -271,15 +274,7 @@ export class IndexComponent implements OnInit { exportData(): void { const data = JSON.stringify(this.storageService.exportData()); - const blob = new Blob([data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const date = new Date().toISOString().slice(0, 10); - const id = new Date().getTime(); - - const anchor = document.createElement('a'); - anchor.href = url; - anchor.download = `${date}-${id}-settings.json`; - anchor.click(); + this.downloadService.downloadFile(data, 'settings', 'json'); this.notificationService.success('Settings exported'); } diff --git a/src/app/types/file.type.ts b/src/app/types/file.type.ts new file mode 100644 index 000000000..3d0544c3f --- /dev/null +++ b/src/app/types/file.type.ts @@ -0,0 +1 @@ +export type FileFormat = 'json' | 'csv'; \ No newline at end of file