added tests for batch import functionality
This commit is contained in:
parent
0f4ce609da
commit
0aeb470793
6 changed files with 466 additions and 3 deletions
|
|
@ -2,7 +2,7 @@ FROM node:lts-alpine as builder
|
||||||
|
|
||||||
WORKDIR /metube
|
WORKDIR /metube
|
||||||
COPY ui ./
|
COPY ui ./
|
||||||
RUN npm ci && \
|
RUN npm install && \
|
||||||
node_modules/.bin/ng build --configuration production
|
node_modules/.bin/ng build --configuration production
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
2
ui/package-lock.json
generated
2
ui/package-lock.json
generated
|
|
@ -24,7 +24,7 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.7.0",
|
"@fortawesome/free-regular-svg-icons": "^6.7.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.0",
|
"@fortawesome/free-solid-svg-icons": "^6.7.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
|
||||||
"@ng-select/ng-select": "^14.0.0",
|
"@ng-select/ng-select": "^14.9.0",
|
||||||
"bootstrap": "^5.3.6",
|
"bootstrap": "^5.3.6",
|
||||||
"ngx-cookie-service": "^19.0.0",
|
"ngx-cookie-service": "^19.0.0",
|
||||||
"ngx-socket-io": "~4.8.0",
|
"ngx-socket-io": "~4.8.0",
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.7.0",
|
"@fortawesome/free-regular-svg-icons": "^6.7.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.0",
|
"@fortawesome/free-solid-svg-icons": "^6.7.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
|
||||||
"@ng-select/ng-select": "^14.0.0",
|
"@ng-select/ng-select": "^14.9.0",
|
||||||
"bootstrap": "^5.3.6",
|
"bootstrap": "^5.3.6",
|
||||||
"ngx-cookie-service": "^19.0.0",
|
"ngx-cookie-service": "^19.0.0",
|
||||||
"ngx-socket-io": "~4.8.0",
|
"ngx-socket-io": "~4.8.0",
|
||||||
|
|
|
||||||
190
ui/src/app/batch-import-core.spec.ts
Normal file
190
ui/src/app/batch-import-core.spec.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
// Core batch import logic tests - isolated from Angular dependencies
|
||||||
|
describe('Batch Import Core Logic', () => {
|
||||||
|
|
||||||
|
// URL validation function (extracted from our component logic)
|
||||||
|
function validateUrls(urlText: string) {
|
||||||
|
const urls = urlText
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map(url => url.trim())
|
||||||
|
.filter(url => url.length > 0);
|
||||||
|
|
||||||
|
const parsedUrlCount = urls.length;
|
||||||
|
|
||||||
|
if (urls.length === 0) {
|
||||||
|
return { valid: [], invalid: [], duplicates: [], parsedUrlCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic URL validation regex
|
||||||
|
const urlRegex = /^https?:\/\/.+/i;
|
||||||
|
const valid: string[] = [];
|
||||||
|
const invalid: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const duplicates: string[] = [];
|
||||||
|
|
||||||
|
urls.forEach(url => {
|
||||||
|
if (seen.has(url)) {
|
||||||
|
if (!duplicates.includes(url)) {
|
||||||
|
duplicates.push(url);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.add(url);
|
||||||
|
|
||||||
|
if (urlRegex.test(url)) {
|
||||||
|
valid.push(url);
|
||||||
|
} else {
|
||||||
|
invalid.push(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { valid, invalid, duplicates, parsedUrlCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('URL Validation Logic', () => {
|
||||||
|
it('should handle empty input', () => {
|
||||||
|
const result = validateUrls('');
|
||||||
|
expect(result.parsedUrlCount).toBe(0);
|
||||||
|
expect(result.valid).toEqual([]);
|
||||||
|
expect(result.invalid).toEqual([]);
|
||||||
|
expect(result.duplicates).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate single valid URL', () => {
|
||||||
|
const result = validateUrls('https://youtube.com/watch?v=test');
|
||||||
|
expect(result.parsedUrlCount).toBe(1);
|
||||||
|
expect(result.valid).toEqual(['https://youtube.com/watch?v=test']);
|
||||||
|
expect(result.invalid).toEqual([]);
|
||||||
|
expect(result.duplicates).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect invalid URLs', () => {
|
||||||
|
const result = validateUrls('invalid-url\nnot-a-url');
|
||||||
|
expect(result.parsedUrlCount).toBe(2);
|
||||||
|
expect(result.valid).toEqual([]);
|
||||||
|
expect(result.invalid).toEqual(['invalid-url', 'not-a-url']);
|
||||||
|
expect(result.duplicates).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect duplicate URLs', () => {
|
||||||
|
const urls = [
|
||||||
|
'https://youtube.com/watch?v=test',
|
||||||
|
'https://youtube.com/watch?v=test',
|
||||||
|
'https://youtube.com/watch?v=unique'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = validateUrls(urls);
|
||||||
|
expect(result.parsedUrlCount).toBe(3);
|
||||||
|
expect(result.valid).toEqual([
|
||||||
|
'https://youtube.com/watch?v=test',
|
||||||
|
'https://youtube.com/watch?v=unique'
|
||||||
|
]);
|
||||||
|
expect(result.invalid).toEqual([]);
|
||||||
|
expect(result.duplicates).toEqual(['https://youtube.com/watch?v=test']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed valid, invalid, and duplicate URLs', () => {
|
||||||
|
const urls = [
|
||||||
|
'https://youtube.com/watch?v=test1',
|
||||||
|
'https://youtube.com/watch?v=test1', // duplicate
|
||||||
|
'invalid-url',
|
||||||
|
'https://youtu.be/test2',
|
||||||
|
'not-a-url'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = validateUrls(urls);
|
||||||
|
expect(result.parsedUrlCount).toBe(5);
|
||||||
|
expect(result.valid).toEqual([
|
||||||
|
'https://youtube.com/watch?v=test1',
|
||||||
|
'https://youtu.be/test2'
|
||||||
|
]);
|
||||||
|
expect(result.invalid).toEqual(['invalid-url', 'not-a-url']);
|
||||||
|
expect(result.duplicates).toEqual(['https://youtube.com/watch?v=test1']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle whitespace and empty lines', () => {
|
||||||
|
const urls = ' https://youtube.com/test \n\n \nhttps://youtu.be/test2\n\n';
|
||||||
|
const result = validateUrls(urls);
|
||||||
|
|
||||||
|
expect(result.parsedUrlCount).toBe(2);
|
||||||
|
expect(result.valid).toEqual([
|
||||||
|
'https://youtube.com/test',
|
||||||
|
'https://youtu.be/test2'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept various valid URL formats', () => {
|
||||||
|
const validUrls = [
|
||||||
|
'https://youtube.com/watch?v=test',
|
||||||
|
'http://youtube.com/watch?v=test',
|
||||||
|
'https://youtu.be/test',
|
||||||
|
'https://www.youtube.com/playlist?list=test',
|
||||||
|
'https://github.com/user/repo',
|
||||||
|
'https://example.com/path/to/resource'
|
||||||
|
];
|
||||||
|
|
||||||
|
validUrls.forEach(url => {
|
||||||
|
const result = validateUrls(url);
|
||||||
|
expect(result.valid).toContain(url, `${url} should be valid`);
|
||||||
|
expect(result.invalid).not.toContain(url, `${url} should not be invalid`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid URL formats', () => {
|
||||||
|
const invalidUrls = [
|
||||||
|
'youtube.com/watch?v=test', // missing protocol
|
||||||
|
'ftp://youtube.com/test', // wrong protocol
|
||||||
|
'not-a-url',
|
||||||
|
'just-text',
|
||||||
|
'www.example.com' // missing protocol
|
||||||
|
];
|
||||||
|
|
||||||
|
invalidUrls.forEach(url => {
|
||||||
|
const result = validateUrls(url);
|
||||||
|
expect(result.invalid).toContain(url, `${url} should be invalid`);
|
||||||
|
expect(result.valid).not.toContain(url, `${url} should not be valid`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle performance with large number of URLs', () => {
|
||||||
|
const urls = Array.from({ length: 1000 }, (_, i) => `https://youtube.com/watch?v=test${i}`);
|
||||||
|
const urlText = urls.join('\n');
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
const result = validateUrls(urlText);
|
||||||
|
const endTime = performance.now();
|
||||||
|
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // Should be fast
|
||||||
|
expect(result.valid.length).toBe(1000);
|
||||||
|
expect(result.parsedUrlCount).toBe(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Real-world URL Testing', () => {
|
||||||
|
it('should validate actual YouTube URLs', () => {
|
||||||
|
const urls = [
|
||||||
|
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||||
|
'https://youtu.be/jNQXAC9IVRw',
|
||||||
|
'https://youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = validateUrls(urls);
|
||||||
|
expect(result.valid.length).toBe(3);
|
||||||
|
expect(result.invalid.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed real URLs with invalid ones', () => {
|
||||||
|
const urls = [
|
||||||
|
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||||
|
'invalid-youtube-url',
|
||||||
|
'https://github.com/alexta69/metube',
|
||||||
|
'just-plain-text',
|
||||||
|
'https://youtu.be/jNQXAC9IVRw'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = validateUrls(urls);
|
||||||
|
expect(result.valid.length).toBe(3);
|
||||||
|
expect(result.invalid.length).toBe(2);
|
||||||
|
expect(result.invalid).toEqual(['invalid-youtube-url', 'just-plain-text']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
228
ui/src/app/batch-import.spec.ts
Normal file
228
ui/src/app/batch-import.spec.ts
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { DownloadsService } from './downloads.service';
|
||||||
|
import { CookieService } from 'ngx-cookie-service';
|
||||||
|
import { MeTubeSocket } from './metube-socket';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import {
|
||||||
|
MockSpeedPipe,
|
||||||
|
MockEtaPipe,
|
||||||
|
MockFileSizePipe,
|
||||||
|
MockMasterCheckboxComponent,
|
||||||
|
MockSlaveCheckboxComponent
|
||||||
|
} from './test-utils';
|
||||||
|
|
||||||
|
describe('Batch Import Functionality', () => {
|
||||||
|
let component: AppComponent;
|
||||||
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
|
let downloadsServiceSpy: jasmine.SpyObj<DownloadsService>;
|
||||||
|
let cookieServiceSpy: jasmine.SpyObj<CookieService>;
|
||||||
|
let socketSpy: jasmine.SpyObj<MeTubeSocket>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const downloadsSpy = jasmine.createSpyObj('DownloadsService', ['add'], {
|
||||||
|
loading: false,
|
||||||
|
queue: new Map(),
|
||||||
|
done: new Map(),
|
||||||
|
queueChanged: of(null),
|
||||||
|
doneChanged: of(null),
|
||||||
|
updated: of(null),
|
||||||
|
configurationChanged: of({}),
|
||||||
|
customDirsChanged: of({ download_dir: [], audio_download_dir: [] }),
|
||||||
|
ytdlOptionsChanged: of({}),
|
||||||
|
configuration: {}
|
||||||
|
});
|
||||||
|
const cookieSpy = jasmine.createSpyObj('CookieService', ['get', 'set', 'check']);
|
||||||
|
const metubeSocketSpy = jasmine.createSpyObj('MeTubeSocket', ['fromEvent']);
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
MockSpeedPipe,
|
||||||
|
MockEtaPipe,
|
||||||
|
MockFileSizePipe,
|
||||||
|
MockMasterCheckboxComponent,
|
||||||
|
MockSlaveCheckboxComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
HttpClientTestingModule,
|
||||||
|
NgbModule,
|
||||||
|
FontAwesomeModule,
|
||||||
|
NgSelectModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: DownloadsService, useValue: downloadsSpy },
|
||||||
|
{ provide: CookieService, useValue: cookieSpy },
|
||||||
|
{ provide: MeTubeSocket, useValue: metubeSocketSpy }
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AppComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
downloadsServiceSpy = TestBed.inject(DownloadsService) as jasmine.SpyObj<DownloadsService>;
|
||||||
|
cookieServiceSpy = TestBed.inject(CookieService) as jasmine.SpyObj<CookieService>;
|
||||||
|
socketSpy = TestBed.inject(MeTubeSocket) as jasmine.SpyObj<MeTubeSocket>;
|
||||||
|
|
||||||
|
// Set up socket mock returns
|
||||||
|
socketSpy.fromEvent.and.returnValue(of({}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Core Batch Import Logic', () => {
|
||||||
|
it('should create component successfully', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize batch import properties', () => {
|
||||||
|
expect(component.batchImportModalOpen).toBe(false);
|
||||||
|
expect(component.batchImportText).toBe('');
|
||||||
|
expect(component.batchImportStatus).toBe('');
|
||||||
|
expect(component.importInProgress).toBe(false);
|
||||||
|
expect(component.cancelImportFlag).toBe(false);
|
||||||
|
expect(component.urlValidationResults).toEqual({ valid: [], invalid: [], duplicates: [] });
|
||||||
|
expect(component.parsedUrlCount).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open batch import modal correctly', () => {
|
||||||
|
component.openBatchImportModal();
|
||||||
|
|
||||||
|
expect(component.batchImportModalOpen).toBe(true);
|
||||||
|
expect(component.batchImportText).toBe('');
|
||||||
|
expect(component.batchImportStatus).toBe('');
|
||||||
|
expect(component.importInProgress).toBe(false);
|
||||||
|
expect(component.cancelImportFlag).toBe(false);
|
||||||
|
expect(component.urlValidationResults).toEqual({ valid: [], invalid: [], duplicates: [] });
|
||||||
|
expect(component.parsedUrlCount).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate URLs correctly', () => {
|
||||||
|
const testUrls = [
|
||||||
|
'https://youtube.com/watch?v=test1',
|
||||||
|
'https://youtube.com/watch?v=test1', // duplicate
|
||||||
|
'invalid-url',
|
||||||
|
'https://youtu.be/test2'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
component.batchImportText = testUrls;
|
||||||
|
component.validateBatchUrls();
|
||||||
|
|
||||||
|
expect(component.parsedUrlCount).toBe(4);
|
||||||
|
expect(component.urlValidationResults.valid).toEqual([
|
||||||
|
'https://youtube.com/watch?v=test1',
|
||||||
|
'https://youtu.be/test2'
|
||||||
|
]);
|
||||||
|
expect(component.urlValidationResults.invalid).toEqual(['invalid-url']);
|
||||||
|
expect(component.urlValidationResults.duplicates).toEqual(['https://youtube.com/watch?v=test1']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty URL input', () => {
|
||||||
|
component.batchImportText = '';
|
||||||
|
component.validateBatchUrls();
|
||||||
|
|
||||||
|
expect(component.parsedUrlCount).toBe(0);
|
||||||
|
expect(component.urlValidationResults).toEqual({ valid: [], invalid: [], duplicates: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle whitespace and empty lines', () => {
|
||||||
|
component.batchImportText = ' https://youtube.com/test \n\n \nhttps://youtu.be/test2\n\n';
|
||||||
|
component.validateBatchUrls();
|
||||||
|
|
||||||
|
expect(component.parsedUrlCount).toBe(2);
|
||||||
|
expect(component.urlValidationResults.valid).toEqual([
|
||||||
|
'https://youtube.com/test',
|
||||||
|
'https://youtu.be/test2'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid URL formats', () => {
|
||||||
|
const invalidUrls = [
|
||||||
|
'youtube.com/watch?v=test', // missing protocol
|
||||||
|
'ftp://youtube.com/test', // wrong protocol
|
||||||
|
'not-a-url',
|
||||||
|
'just-text'
|
||||||
|
];
|
||||||
|
|
||||||
|
component.batchImportText = invalidUrls.join('\n');
|
||||||
|
component.validateBatchUrls();
|
||||||
|
|
||||||
|
expect(component.urlValidationResults.valid).toEqual([]);
|
||||||
|
expect(component.urlValidationResults.invalid).toEqual(invalidUrls);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Import Process Logic', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
downloadsServiceSpy.add.and.returnValue(of({ status: 'ok' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not start import with no valid URLs', () => {
|
||||||
|
component.batchImportText = 'invalid-url\nnot-a-url';
|
||||||
|
spyOn(window, 'alert');
|
||||||
|
|
||||||
|
component.startBatchImport();
|
||||||
|
|
||||||
|
expect(window.alert).toHaveBeenCalledWith('No valid URLs found. Please check your URLs and try again.');
|
||||||
|
expect(component.importInProgress).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show confirmation dialog with mixed URLs', () => {
|
||||||
|
component.batchImportText = 'https://youtube.com/test\ninvalid-url';
|
||||||
|
spyOn(window, 'confirm').and.returnValue(false);
|
||||||
|
|
||||||
|
component.startBatchImport();
|
||||||
|
|
||||||
|
expect(window.confirm).toHaveBeenCalledWith('Found 1 invalid URLs that will be skipped. Continue with 1 valid URLs?');
|
||||||
|
expect(component.importInProgress).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start import with valid URLs only', () => {
|
||||||
|
component.batchImportText = 'https://youtube.com/test1\nhttps://youtube.com/test2';
|
||||||
|
|
||||||
|
component.startBatchImport();
|
||||||
|
|
||||||
|
expect(component.importInProgress).toBe(true);
|
||||||
|
expect(component.batchImportStatus).toBe('Starting to import 2 URLs...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cancellation correctly', () => {
|
||||||
|
component.importInProgress = true;
|
||||||
|
component.batchImportStatus = 'Importing...';
|
||||||
|
|
||||||
|
component.cancelBatchImport();
|
||||||
|
|
||||||
|
expect(component.cancelImportFlag).toBe(true);
|
||||||
|
expect(component.batchImportStatus).toBe('Importing... Cancelling...');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('URL Validation Edge Cases', () => {
|
||||||
|
it('should handle large number of URLs', () => {
|
||||||
|
const urls = Array.from({ length: 100 }, (_, i) => `https://youtube.com/watch?v=test${i}`);
|
||||||
|
component.batchImportText = urls.join('\n');
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
component.validateBatchUrls();
|
||||||
|
const endTime = performance.now();
|
||||||
|
|
||||||
|
expect(endTime - startTime).toBeLessThan(50); // Should be fast
|
||||||
|
expect(component.urlValidationResults.valid.length).toBe(100);
|
||||||
|
expect(component.parsedUrlCount).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect multiple duplicates correctly', () => {
|
||||||
|
const duplicateUrl = 'https://youtube.com/watch?v=duplicate';
|
||||||
|
const urls = [duplicateUrl, duplicateUrl, duplicateUrl, 'https://youtube.com/watch?v=unique'];
|
||||||
|
component.batchImportText = urls.join('\n');
|
||||||
|
|
||||||
|
component.validateBatchUrls();
|
||||||
|
|
||||||
|
expect(component.urlValidationResults.valid.length).toBe(2); // duplicate + unique
|
||||||
|
expect(component.urlValidationResults.duplicates).toEqual([duplicateUrl]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
45
ui/src/app/test-utils.ts
Normal file
45
ui/src/app/test-utils.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
// Mock pipes for testing
|
||||||
|
@Pipe({ name: 'speed' })
|
||||||
|
export class MockSpeedPipe implements PipeTransform {
|
||||||
|
transform(value: any): string {
|
||||||
|
return value ? `${value} MB/s` : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({ name: 'eta' })
|
||||||
|
export class MockEtaPipe implements PipeTransform {
|
||||||
|
transform(value: any): string {
|
||||||
|
return value ? `${value}s` : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({ name: 'fileSize' })
|
||||||
|
export class MockFileSizePipe implements PipeTransform {
|
||||||
|
transform(value: any): string {
|
||||||
|
return value ? `${value} MB` : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock components for testing
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-master-checkbox',
|
||||||
|
template: '<input type="checkbox" [id]="id">'
|
||||||
|
})
|
||||||
|
export class MockMasterCheckboxComponent {
|
||||||
|
@Input() id: string = '';
|
||||||
|
@Input() list: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-slave-checkbox',
|
||||||
|
template: '<input type="checkbox" [id]="id">'
|
||||||
|
})
|
||||||
|
export class MockSlaveCheckboxComponent {
|
||||||
|
@Input() id: string = '';
|
||||||
|
@Input() master: any;
|
||||||
|
@Input() checkable: any;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue