From e998951be9a3e569476c5fbbd81810290a4e3755 Mon Sep 17 00:00:00 2001 From: cepurwin <113333536+cepurwin@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:45:14 +0200 Subject: [PATCH] Prod API and info popups (#98) * Daniel/e2e tests (#93) * Add files via upload * Update playwright.config.ts * Delete tests/example.spec.ts * Update playwright.yml * Update playwright.yml * Cornelius/dev (#94) * Implemented Tooltips: - file format explanations - info that n dimensions are being supported - info that t-SNE and PCA is used - info that local execution can push a device to its limits - info that the download file format is png Deleted unnecessary routeToLogin() method. * Changes by cedricpurwin * Update input.component.html Correction of previous commit: Made the expert options disable again when no file is uploaded * Update input.component.html Fixed syntax error (missing ") * Update input.component.html Made more precise tooltop regarding PCA and t-SNE * Update input.component.html Made even more precise tooltip regarding t-SNE and PCA on request of Klaus --------- Co-authored-by: cookie.exe Co-authored-by: Cornelius Daemberg <108803070+Splashed01@users.noreply.github.com> * Update input.component.html (#95) * api service tests * Switched to Prod-API * Switched to Prod-API * removed console.log --------- Co-authored-by: REDDERD <143699981+REDDERD@users.noreply.github.com> Co-authored-by: cookie.exe Co-authored-by: Cornelius Daemberg <108803070+Splashed01@users.noreply.github.com> Co-authored-by: david-2031 Co-authored-by: david-2031 <113417732+david-2031@users.noreply.github.com> Co-authored-by: REDDERD --- .github/workflows/playwright.yml | 19 +- playwright.config.ts | 5 - src/app/app.module.ts | 4 +- .../chart/chart.component.html | 2 +- .../home/home-services/api.service.spec.ts | 54 +++- src/app/home/home-services/api.service.ts | 9 +- src/app/home/home.component.ts | 4 - src/app/home/input/input.component.css | 3 + src/app/home/input/input.component.html | 18 +- tests/example.spec.ts | 18 -- tests/moon.csv | 301 ++++++++++++++++++ tests/test-1.spec.ts | 27 ++ tests/test-2.spec.ts | 14 + tests/test-3.spec.ts | 30 ++ tests/test.txt | 1 + 15 files changed, 466 insertions(+), 43 deletions(-) delete mode 100644 tests/example.spec.ts create mode 100644 tests/moon.csv create mode 100644 tests/test-1.spec.ts create mode 100644 tests/test-2.spec.ts create mode 100644 tests/test-3.spec.ts create mode 100644 tests/test.txt diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5077922..b596827 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,9 +1,14 @@ name: Playwright Tests on: + schedule: + - cron: '35 4 * * *' push: - branches: '**' + branches: [ main, test-deployment ] + pull_request: + branches: [ main, test-deployment ] + jobs: - test: + E2E_Tests: timeout-minutes: 60 runs-on: ubuntu-latest steps: @@ -13,6 +18,16 @@ jobs: node-version: 18 - name: Install dependencies run: npm ci + - name: Install Angular CLI + run: npm install -g @angular/cli + - name: Start local server in the background + run: ng serve & + - name: Wait for the server to start + run: | + until nc -z -w5 localhost 4200; do + echo 'Waiting for the local server to start...' + sleep 5 + done - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Run Playwright tests diff --git a/playwright.config.ts b/playwright.config.ts index 301801e..1a93a2c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -37,11 +37,6 @@ export default defineConfig({ use: { ...devices['Desktop Chrome'] }, }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - { name: 'webkit', use: { ...devices['Desktop Safari'] }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index fc81d11..cf0a339 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,6 +23,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle' import { TableComponent } from './home/chart-container/table/table.component' import { MatTabsModule } from '@angular/material/tabs' import { MatTableModule } from '@angular/material/table' +import { MatTooltipModule } from '@angular/material/tooltip' const routes: Routes = [{ path: 'home', component: HomeComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' }] @@ -53,7 +54,8 @@ const routes: Routes = [{ path: 'home', component: HomeComponent }, MatExpansionModule, MatSlideToggleModule, MatTabsModule, - MatTableModule + MatTableModule, + MatTooltipModule ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/home/chart-container/chart/chart.component.html b/src/app/home/chart-container/chart/chart.component.html index 464b244..7e2d8c3 100644 --- a/src/app/home/chart-container/chart/chart.component.html +++ b/src/app/home/chart-container/chart/chart.component.html @@ -3,7 +3,7 @@ {{ chart }} - + diff --git a/src/app/home/home-services/api.service.spec.ts b/src/app/home/home-services/api.service.spec.ts index 762c9f9..c9c88cd 100644 --- a/src/app/home/home-services/api.service.spec.ts +++ b/src/app/home/home-services/api.service.spec.ts @@ -1,21 +1,71 @@ import { TestBed } from '@angular/core/testing' - import { ApiService } from './api.service' import { HttpClientModule } from '@angular/common/http' +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' describe('ApiService', () => { let service: ApiService + let httpMock: HttpTestingController beforeEach(() => { TestBed.configureTestingModule({ imports: [ - HttpClientModule + HttpClientModule, + HttpClientTestingModule ] }) service = TestBed.inject(ApiService) + httpMock = TestBed.inject(HttpTestingController) }) it('should be created', () => { expect(service).toBeTruthy() }) + + it('should perform basic 2d api call', () => { + const mockResponse = {} + const mockFile = new File(['test'], 'test.csv', { type: 'text/csv' }) + + service.postKmeans(mockFile, 1, 2, 3, 'euclidean', 'auto', false).subscribe(response => { + expect(response).toEqual(mockResponse) + }) + + const req = httpMock.expectOne(request => request.url.startsWith('https://app.axellotl.de/basic/perform-2d-kmeans/')) + expect(req.request.method).toBe('POST') + expect(req.request.params.get('column1')).toBe('1') + expect(req.request.params.get('column2')).toBe('2') + expect(req.request.params.get('k_clusters')).toBe('3') + expect(req.request.params.get('distanceMetric')).toBe('euclidean') + expect(req.request.params.get('clusterDetermination')).toBe('auto') + + req.flush(mockResponse) + }) + + it('should perform n dimensional api call with k', () => { + const mockResponse = {} + const mockFile = new File(['test'], 'test.csv', { type: 'text/csv' }) + + service.postKmeans(mockFile, 1, 2, 3, 'euclidean', 'auto', true).subscribe(response => { + expect(response).toEqual(mockResponse) + }) + + const req = httpMock.expectOne(request => request.url.startsWith('https://app.axellotl.de/basic/perform-nd-kmeans/')) + expect(req.request.method).toBe('POST') + + req.flush(mockResponse) + }) + + it('should perform n dimensional api call without k', () => { + const mockResponse = {} + const mockFile = new File(['test'], 'test.csv', { type: 'text/csv' }) + + service.postKmeans(mockFile, 1, 2, undefined, 'euclidean', 'auto', true).subscribe(response => { + expect(response).toEqual(mockResponse) + }) + + const req = httpMock.expectOne(request => request.url.startsWith('https://app.axellotl.de/basic/perform-nd-kmeans/')) + expect(req.request.method).toBe('POST') + + req.flush(mockResponse) + }) }) diff --git a/src/app/home/home-services/api.service.ts b/src/app/home/home-services/api.service.ts index f0c7f47..6534f36 100644 --- a/src/app/home/home-services/api.service.ts +++ b/src/app/home/home-services/api.service.ts @@ -16,16 +16,16 @@ export class ApiService { nDimensional?: boolean ): Observable { let Params: HttpParams = new HttpParams() - let url = 'https://beta.axellotl.de/advanced/perform-advanced-2d-kmeans/' + let url = 'https://app.axellotl.de/advanced/perform-advanced-2d-kmeans/' if (nDimensional === true) { if (kCluster !== 0) { - url = 'https://beta.axellotl.de/basic/perform-nd-kmeans/' + url = 'https://app.axellotl.de/basic/perform-nd-kmeans/' } else { - url = 'https://beta.axellotl.de/advanced/perform-advanced-nd-kmeans/' + url = 'https://app.axellotl.de/advanced/perform-advanced-nd-kmeans/' } } else { if (kCluster !== 0) { - url = 'https://beta.axellotl.de/basic/perform-2d-kmeans/' + url = 'https://app.axellotl.de/basic/perform-2d-kmeans/' } } if (nDimensional === false) { @@ -48,7 +48,6 @@ export class ApiService { const formData = new FormData() formData.append('file', new Blob([csv], { type: 'text/csv' }), csv.name) - return this.http.post( url, formData, diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 54d7131..fd25ba2 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -29,8 +29,4 @@ export class HomeComponent { public handleLoading (status: boolean): void { this.isLoading = status } - - public routeToLogin (): void { - void this.router.navigate(['/login']) - } } diff --git a/src/app/home/input/input.component.css b/src/app/home/input/input.component.css index 57fd426..973403d 100644 --- a/src/app/home/input/input.component.css +++ b/src/app/home/input/input.component.css @@ -33,6 +33,9 @@ .button-row{ horiz-align: center; + display: grid; + text-align: left; + grid-template-columns: 20% 60% 20%; } } .dropzone:hover{ diff --git a/src/app/home/input/input.component.html b/src/app/home/input/input.component.html index fdac853..82dcab2 100644 --- a/src/app/home/input/input.component.html +++ b/src/app/home/input/input.component.html @@ -9,9 +9,14 @@

Ziehe eine .csv / .xlsx Datei per Drag & Drop hierher

oder

- +
+
+
@@ -31,17 +36,20 @@

oder

Spaltenauswahl
+ + [class.has-error]="clusterInputFormGroup.get('selectedColumns')?.hasError('twoColumnsRequired')" > Spalten - + {{column}} - + Bitte mindestens 2 Spalten auswählen. + +
Anzahl der Cluster (optional)
@@ -69,7 +77,7 @@

oder

- Lokal im Browser ausführen
+ Lokal im Browser ausführen
diff --git a/tests/example.spec.ts b/tests/example.spec.ts deleted file mode 100644 index 54a906a..0000000 --- a/tests/example.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('has title', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); - -test('get started link', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Click the get started link. - await page.getByRole('link', { name: 'Get started' }).click(); - - // Expects page to have a heading with the name of Installation. - await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); -}); diff --git a/tests/moon.csv b/tests/moon.csv new file mode 100644 index 0000000..88cc19e --- /dev/null +++ b/tests/moon.csv @@ -0,0 +1,301 @@ +X,Y +0.345273,-0.208154 +-0.178863,0.997116 +0.139864,-0.00816106 +0.989729,0.135932 +0.876292,0.329633 +-0.990427,0.0130064 +-0.313825,0.85623 +0.766826,0.619353 +0.831395,-0.509516 +-0.348057,0.918276 +1.78824,-0.114109 +1.92848,0.394368 +0.88661,-0.485566 +1.01417,0.392038 +0.0256357,0.413997 +0.459415,-0.339288 +0.543003,0.746885 +0.521373,0.86692 +0.832241,-0.506842 +0.228714,-0.132289 +0.645342,-0.413599 +-0.0102884,0.289161 +0.928024,-0.53394 +0.35974,-0.19838 +-1.0033,0.0362279 +-0.931326,0.418082 +0.458718,0.897081 +-0.519697,0.79393 +1.11974,0.0469094 +0.868972,0.596808 +1.57688,-0.317953 +-0.00709064,0.977255 +0.609482,-0.390038 +0.025369,0.244698 +0.141554,0.122038 +-0.884067,0.627561 +-0.876863,0.485725 +0.0431588,1.1054 +0.263298,-0.152355 +-0.348341,0.942855 +0.954283,0.470252 +1.03961,0.150999 +0.602029,0.777236 +-0.577273,0.997667 +-0.00548894,0.992061 +0.61094,0.690547 +0.847121,0.414898 +1.79593,0.0636744 +0.192367,1.00257 +1.90326,0.267722 +-0.0667966,1.11794 +0.879472,0.0954256 +0.966716,-0.531546 +-0.932856,0.236957 +0.385387,-0.248196 +1.94431,0.255607 +0.906102,0.508979 +-0.573283,0.749328 +-0.906203,0.22073 +1.23185,-0.459979 +1.95913,0.387162 +-0.520412,0.946123 +0.642885,0.805303 +1.92515,0.143617 +-0.730748,0.639982 +-0.604587,0.903118 +-1.00978,0.14368 +1.0511,-0.587686 +0.994376,0.237552 +0.855828,0.598758 +1.41191,-0.378194 +0.817929,0.558339 +0.417741,0.909885 +1.65915,-0.192481 +0.66123,0.869263 +1.00852,-0.498705 +-0.89544,0.47388 +0.561455,-0.398044 +-0.636276,0.797134 +0.176865,0.985011 +0.467107,-0.309741 +-0.528008,0.82411 +1.8959,0.098132 +1.9435,0.0257886 +2.00286,0.439573 +1.34101,-0.463565 +-0.401831,0.853312 +0.577246,0.864666 +1.38918,-0.403559 +0.0237393,0.0448697 +0.402617,-0.304469 +1.65412,-0.258803 +-1.01959,0.415701 +0.0279268,0.137277 +0.0802436,0.445686 +0.833033,0.450331 +0.548314,0.836928 +1.54441,-0.373491 +0.0743096,0.210425 +1.45033,-0.346427 +-0.836614,0.540852 +0.0984217,-0.0951621 +-0.747725,0.722537 +1.87214,0.0530072 +-1.00814,0.084602 +0.175566,0.879784 +0.978923,0.0803822 +0.415112,-0.279221 +1.79153,-0.0819537 +1.97457,0.345057 +1.25711,-0.46894 +0.304179,0.974818 +0.649459,0.767919 +0.0494354,0.982683 +0.0125241,0.378814 +0.962128,-0.483045 +-1.0614,0.059572 +0.0671535,0.0399212 +1.549,-0.358268 +0.692653,-0.413129 +1.96107,0.414998 +0.999925,0.186997 +0.394783,0.937349 +2.05014,0.498337 +-0.904176,0.176835 +1.69095,-0.0721716 +0.294487,-0.16629 +0.780501,0.639217 +0.0344366,0.383925 +-0.910676,0.39458 +-1.01019,0.070952 +0.761197,-0.441565 +0.195525,0.953816 +0.566854,0.895482 +0.784141,-0.44465 +0.199835,-0.133255 +0.979094,0.109992 +1.99266,0.320169 +1.86619,-0.103223 +1.55915,-0.320221 +-0.804722,0.693083 +0.903874,-0.507537 +-0.366615,0.936957 +-0.924741,0.25622 +2.01258,0.372269 +-0.0923796,0.37861 +-0.947624,0.210125 +0.994604,0.148877 +1.82615,-0.267841 +0.847365,0.562224 +0.0354297,0.0947459 +-0.00819748,1.07148 +0.211554,1.05905 +0.815121,0.576818 +1.09687,-0.540406 +-0.75881,0.656908 +-0.895234,0.382667 +0.926857,0.473739 +0.994278,-0.579053 +-0.728932,0.771656 +-0.113591,0.979641 +-0.90053,0.365969 +-0.751877,0.647591 +0.119562,0.156458 +0.486839,-0.452873 +0.706344,-0.540719 +-0.881608,0.378023 +1.82659,-0.158975 +1.21987,-0.429234 +0.895795,-0.400911 +0.930646,0.481678 +0.39518,0.965011 +0.951269,0.38936 +1.88957,0.0393342 +1.38822,-0.43593 +0.8114,-0.565037 +0.0860837,0.136589 +-0.977354,0.0532105 +1.52026,-0.365572 +-0.0424885,0.406945 +0.015209,0.317766 +1.85982,-0.0953652 +1.66275,-0.277926 +1.46118,-0.434718 +0.492761,-0.351916 +-0.95051,0.449402 +1.15643,-0.462068 +0.206712,0.916379 +0.0141695,0.490234 +1.96165,0.252301 +-0.901829,0.185347 +0.42663,-0.2528 +1.87256,0.032617 +0.577198,-0.325382 +2.01864,0.229042 +-0.230808,0.930004 +1.14598,-0.560386 +-0.20989,0.958612 +0.57273,0.827667 +0.0538726,0.962749 +-1.08069,0.0576707 +0.209848,0.040698 +-0.689327,0.770797 +0.18723,-0.0572674 +-0.846332,0.503023 +1.91047,0.147301 +1.60486,-0.312189 +0.458022,0.7939 +0.930795,0.326421 +1.843,0.125321 +2.03145,0.319452 +-0.0465428,1.01403 +1.9026,0.198357 +0.5291,0.871184 +0.819017,-0.452872 +0.0052791,0.365353 +1.65989,-0.362234 +-0.47219,0.811597 +-0.44777,0.895138 +1.04418,0.157884 +0.153406,-0.043706 +0.0890057,0.161633 +-0.255216,1.02589 +-0.246145,0.946611 +-0.348517,0.855369 +-0.474115,0.927414 +-0.875199,0.50406 +0.276974,-0.118893 +0.934591,0.109751 +-0.478228,0.868799 +1.76019,-0.265353 +-0.0184325,0.970557 +-0.635942,0.775556 +1.22713,-0.463031 +0.938532,0.316907 +-0.879918,0.683499 +1.69073,-0.171608 +0.411043,-0.38426 +-0.713412,0.647198 +-0.916479,0.313215 +1.767,-0.130494 +0.976297,0.0744707 +0.730724,0.717312 +-1.01142,0.0758532 +0.306195,0.888109 +0.363122,-0.356223 +1.8322,-0.0563826 +1.35587,-0.458162 +1.20234,-0.488274 +-0.0399336,1.07689 +0.699748,-0.529041 +0.102665,0.0427968 +2.00386,0.227385 +0.952396,0.381729 +-0.242595,0.985758 +-0.779411,0.586655 +0.589451,0.752888 +1.52425,-0.23715 +1.67781,-0.17489 +-0.0807875,1.00393 +-0.784226,0.687386 +0.270429,-0.312763 +1.95563,0.433449 +0.625159,-0.534155 +1.58463,-0.309241 +0.392212,-0.315407 +-0.067333,0.306594 +-0.74102,0.542934 +0.988808,0.209791 +1.96746,0.454003 +0.78551,0.639505 +1.0072,-0.523221 +1.20905,-0.453824 +0.328852,-0.217215 +1.07217,-0.67199 +0.427833,0.919332 +1.31496,-0.423161 +0.769932,0.796064 +1.19809,-0.526909 +-0.00021252,0.172826 +1.33076,-0.4608 +0.268423,-0.104214 +0.919024,0.446184 +0.648768,-0.420543 +1.78673,-0.124814 +0.0104656,0.311505 +0.21495,0.957543 +-0.231698,0.909869 +-0.405018,0.990484 +0.378296,0.875175 +1.28482,-0.420961 +0.318645,0.940878 +0.100213,0.0365805 +0.680349,-0.408769 +0.984528,0.0669175 +-0.648876,0.795359 +0.462921,0.896035 +0.084172,1.00922 +0.793378,0.718794 +0.15096,-0.084897 diff --git a/tests/test-1.spec.ts b/tests/test-1.spec.ts new file mode 100644 index 0000000..bb9f6fc --- /dev/null +++ b/tests/test-1.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '@playwright/test'; +import exp from 'constants'; + +test('sucessfull upload', async ({ page }) => { + await page.goto('http://localhost:4200/'); + await page.reload(); + expect(page.getByRole('button', { name: 'Lade eine Datei hoch' })).toBeVisible(); + expect(page.getByText('[object Object]')).toBeVisible(); + await page.getByRole('button', { name: 'Lade eine Datei hoch' }).click(); + const inputUploadHandle = await page.$('input[type=file]'); + await inputUploadHandle.setInputFiles('tests/moon.csv'); + expect(page.getByRole('button', { name: 'Experteneinstellungen' })).toBeVisible(); + expect(page.getByText('insert_drive_filemoon.csv')).toBeVisible(); + await page.getByRole('button', { name: 'Experteneinstellungen' }).click(); + await page.getByLabel('execute locally').click(); + await page.getByRole('button', { name: 'K-Means durchführen' }).click(); + await page.getByText('Tabelle').click(); + expect(page.getByRole('cell', { name: 'Centroid 1' })).toBeVisible(); + await page.getByRole('cell', { name: 'Centroid 1' }).getByRole('button').click(); + await page.getByText('Diagramm').click(); + + expect(page.getByText('[object Object]')).toBeVisible(); + await page.getByText('[object Object]').click(); + +}); + + diff --git a/tests/test-2.spec.ts b/tests/test-2.spec.ts new file mode 100644 index 0000000..d112e3e --- /dev/null +++ b/tests/test-2.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from '@playwright/test'; + +test('wrong data format', async ({ page }) => { + await page.goto('http://localhost:4200/'); + await page.reload(); + expect(page.getByText('[object Object]')).toBeVisible(); + await page.getByRole('button', { name: 'Lade eine Datei hoch' }).click(); + const inputUploadHandle = await page.$('input[type=file]'); + await inputUploadHandle.setInputFiles('tests/test.txt'); + + expect(page.getByText('Falsches Dateiformat')).toBeVisible(); + await page.getByText('[object Object]').click(); + +}); diff --git a/tests/test-3.spec.ts b/tests/test-3.spec.ts new file mode 100644 index 0000000..71affc2 --- /dev/null +++ b/tests/test-3.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test'; + +test('sucessful upload with changed settings', async ({ page }) => { + await page.goto('http://localhost:4200/'); + await page.reload(); + await page.getByRole('button', { name: 'Lade eine Datei hoch' }).click(); + const inputUploadHandle = await page.$('input[type=file]'); + await inputUploadHandle.setInputFiles('tests/moon.csv'); + expect(page.getByText('[object Object]')).toBeVisible(); + await page.getByRole('button', { name: 'Experteneinstellungen' }).click(); + expect(page.getByRole('button', { name: 'Experteneinstellungen' })).toBeVisible(); + expect(page.getByText('insert_drive_filemoon.csv')).toBeVisible(); + await page.getByLabel('execute locally').click(); + await page.getByRole('button', { name: 'Manhattan' }).click(); + await page.getByLabel('k definieren (optional)').click(); + await page.getByLabel('k definieren (optional)').fill('5'); + await page.getByLabel('X, Y').locator('path').click(); + await page.getByRole('option', { name: 'X' }).locator('mat-pseudo-checkbox').click(); + await page.locator('.cdk-overlay-backdrop').click(); + expect(page.getByText('Bitte mindestens 2 Spalten auswählen.')).toBeVisible(); + await page.getByLabel('Spalten').locator('svg').click(); + await page.getByRole('option', { name: 'X' }).locator('mat-pseudo-checkbox').click(); + await page.locator('.cdk-overlay-backdrop').click(); + await page.getByRole('button', { name: 'K-Means durchführen' }).click(); + expect(page.getByText('[object Object]')).toBeVisible(); + await page.getByText('Tabelle').click(); + expect(page.getByRole('cell', { name: 'Centroid 5' })).toBeVisible(); + await page.getByRole('cell', { name: 'Centroid 5' }).click(); + await page.getByRole('cell', { name: 'Centroid 5' }).getByRole('button').click(); +}); \ No newline at end of file diff --git a/tests/test.txt b/tests/test.txt new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/tests/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file