From fa58a9c7d6536e54c0af9a9ab2d4eff16f7ae079 Mon Sep 17 00:00:00 2001 From: Aaron Louie Date: Mon, 23 Nov 2020 10:46:27 -0500 Subject: [PATCH] Reduces timeout to 1second. Tweaks 1D barcode design. Prints multiple labels. --- e2e/protractor.conf.js | 3 +- e2e/src/app.e2e-spec.ts | 62 ++++++++++- e2e/src/app.po.ts | 59 ++++++++++- src/app/_util/qrCode.ts | 14 +-- src/app/app-routing.module.ts | 6 ++ src/app/app.module.ts | 2 + src/app/config/defaults.ts | 2 +- .../rectangle-3x1-code128.component.svg | 31 ++++-- .../label-layout.component.spec.ts | 2 +- .../label-layout/label-layout.component.ts | 6 +- src/app/models/appDefaults.interface.ts | 4 +- src/app/models/labelLayout.interface.ts | 2 +- .../multiple-labels.component.html | 17 +++ .../multiple-labels.component.scss | 48 +++++++++ .../multiple-labels.component.spec.ts | 25 +++++ .../multiple-labels.component.ts | 100 ++++++++++++++++++ .../print-layout/print-layout.component.html | 2 +- .../print-layout/print-layout.component.ts | 2 +- src/app/print/print.component.html | 9 +- src/app/print/print.component.spec.ts | 2 +- src/app/print/print.component.ts | 9 +- src/app/sample/sample.component.html | 13 +-- src/app/sample/sample.component.scss | 2 - src/app/sample/sample.component.ts | 32 +++--- src/app/services/api.service.ts | 3 +- src/app/settings/settings.component.html | 11 +- 26 files changed, 396 insertions(+), 72 deletions(-) create mode 100644 src/app/multiple-labels/multiple-labels.component.html create mode 100644 src/app/multiple-labels/multiple-labels.component.scss create mode 100644 src/app/multiple-labels/multiple-labels.component.spec.ts create mode 100644 src/app/multiple-labels/multiple-labels.component.ts diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js index 74f5c8c..0720f4f 100644 --- a/e2e/protractor.conf.js +++ b/e2e/protractor.conf.js @@ -13,7 +13,8 @@ exports.config = { './src/**/*.e2e-spec.ts' ], capabilities: { - browserName: 'chrome' + browserName: 'chrome', + binary: '/usr/bin/google-chrome-stable' }, directConnect: true, baseUrl: 'http://localhost:4200/', diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts index 69ed2b5..68e2bde 100644 --- a/e2e/src/app.e2e-spec.ts +++ b/e2e/src/app.e2e-spec.ts @@ -20,11 +20,65 @@ describe('COVID19 Testing Kiosk App', () => { page.clickAndExpectRoute('#nav_settings', '/settings'); }); - it('should navigate to home screen', () => { - page.clickAndExpectRoute('#nav_home', '/'); - }); - it('should navigate back to sample input screen', () => { page.clickAndExpectRoute('#nav_home', '/'); }); + + it('should return to settings screen', async () => { + page.clickAndExpectRoute('#nav_settings', '/settings'); + }); + + it('should change location', async () => { + page.inputText('.location-input input', '9999', true); + }); + + it('should change label layout', async () => { + const dropdownSelector = 'mat-select-trigger.selected-label-layout'; + const optionSelector = '.mat-select-panel .mat-option:nth-child(3) .label-layout-name'; + page.waitForVisible(dropdownSelector); + + const selectedTextBefore = await page.getElement(dropdownSelector).getText(); + page.clickElement(dropdownSelector); + page.waitForVisible(optionSelector); + + const optionText = await page.getElement(optionSelector).getText(); + expect(selectedTextBefore).not.toEqual(optionText); + + page.clickElement(optionSelector); + const selectedText = await page.getElement(dropdownSelector).getText(); + expect(selectedText).toEqual(optionText); + }); + + it('should save settings changes', async () => { + const locSelector = '.location-input input'; + const dropdownSelector = 'mat-select-trigger.selected-label-layout'; + const locIdBefore = await page.getElement(locSelector).getAttribute('value'); + const layoutBefore = await page.getElement(dropdownSelector).getAttribute('value'); + + page.clickAndExpectRoute('#btn_save', '/'); + const navLocation = await page.getElement('#nav_location').getText(); + expect(navLocation).toContain(locIdBefore); + + page.clickAndExpectRoute('#nav_settings', '/settings'); + const locIdAfter = await page.getElement(locSelector).getAttribute('value'); + expect(locIdBefore).toEqual(locIdAfter); + const layoutAfter = await page.getElement(dropdownSelector).getAttribute('value'); + expect(layoutBefore).toEqual(layoutAfter); + + page.clickAndExpectRoute('#btn_save', '/'); + }); + + it('should enter a sample', async () => { + const studentId = '987654321'; + const computingId = 'ABC123'; + const idFieldSelector = '.cardnum-input input'; + const initialsFieldSelector = '.initials-input input'; + page.inputText(idFieldSelector, studentId); + page.inputText(initialsFieldSelector, computingId); + page.clickAndExpectRoute('#btn_next', /print/); + page.waitForVisible('#btn_print'); + const labelText = page.getElement('app-print-layout').getText(); + expect(labelText).toContain(studentId); + expect(labelText).toContain(computingId); + }); }); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts index 8cd9260..06ae8a3 100644 --- a/e2e/src/app.po.ts +++ b/e2e/src/app.po.ts @@ -1,6 +1,8 @@ -import {browser, by, element, ElementArrayFinder, ElementFinder, ExpectedConditions} from 'protractor'; +import {browser, by, element, ElementArrayFinder, ElementFinder, ExpectedConditions, protractor} from 'protractor'; export class AppPage { + browser = browser; + navigateTo() { return browser.get(browser.baseUrl) as Promise; } @@ -81,9 +83,22 @@ export class AppPage { }); } + async switchFocusToPrintDialog() { + const browserWindowHandles = await browser.getAllWindowHandles(); + console.log('browserWindowHandles', browserWindowHandles); + + const driver = browser.driver; + const windowHandles = await driver.getAllWindowHandles(); + console.log('windowHandles', windowHandles); + return driver.switchTo().window(windowHandles[0]); + } + waitFor(t: number) { return browser.sleep(t); } + waitForAngularEnabled(enabled: boolean) { + return browser.waitForAngularEnabled(enabled); + } waitForClickable(selector: string) { const e = this.getElement(selector); @@ -95,9 +110,45 @@ export class AppPage { return browser.wait(ExpectedConditions.invisibilityOf(e), 5000); } - waitForVisible(selector: string) { - const e = this.getElement(selector); - return browser.wait(ExpectedConditions.visibilityOf(e), 5000); + + // If given CSS selector is found on the page, waits 5 seconds for the element to become visible. If it's not found + // on the page, recursively calls itself maxLoops number of times, waiting 1 second between each call, until the + // element becomes present. + async waitForVisible(selector: string, maxLoops = 5) { + const numElements = await this.getElements(selector).count(); + if (numElements > 0) { + const e = await this.getElement(selector); + return browser.wait( + ExpectedConditions.visibilityOf(e), + 5000, + `Element "${selector}" is still not visible after waiting for 5 seconds.` + ); + } else if (maxLoops > 0) { + await this.waitFor(1000); + await this.waitForVisible(selector, maxLoops - 1); + } else { + expect(numElements).toBeGreaterThan(0, `Element "${selector}" is not present on the page.`); + } } + inputText(selector: string, textToEnter: string, clearFirst?: boolean) { + expect(this.getElements(selector).count()).toEqual(1); + const field = this.getElement(selector); + this.waitForAngularEnabled(true); + this.waitForVisible(selector, 100); + + if (clearFirst) { + field.clear(); + expect(field.getAttribute('value')).toEqual(''); + } + + field.sendKeys(textToEnter); + expect(field.getAttribute('value')).toEqual(textToEnter); + } + + async pressTabKey100Times() { + for (let i = 0; i < 100; i++) { + await browser.actions().sendKeys(protractor.Key.TAB).perform(); + } + } } diff --git a/src/app/_util/qrCode.ts b/src/app/_util/qrCode.ts index fb372bc..7648c08 100644 --- a/src/app/_util/qrCode.ts +++ b/src/app/_util/qrCode.ts @@ -1,7 +1,7 @@ import {formatDate} from '@angular/common'; export const createQrCodeValue = ( - barCode: string, + cardNum: string, initials: string, dateCreated: Date, locationId: string, @@ -9,13 +9,15 @@ export const createQrCodeValue = ( barcodeType: string ): string => { const is1D = (barcodeType === 'code128'); - const dateFormat = is1D ? 'yyMMdd' : 'yyyyMMddHHmm'; - const locId = is1D ? locationId.slice(3, 4) : locationId; + const compId = is1D ? '' : initials.toUpperCase(); + const longDate = formatDate(dateCreated, 'yyyyMMddHHmm', 'en-us'); + const dateString = is1D ? longDate.slice(3, 10) : longDate; + const locId = is1D ? '' : locationId; const valArray = [ - barCode, - initials.toUpperCase(), - formatDate(dateCreated, dateFormat, 'en-us'), + cardNum, + compId, + dateString, locId, ]; return valArray.join(delimiter); diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 785511a..bf0a3d0 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -5,6 +5,7 @@ import {CountComponent} from './count/count.component'; import {PrintComponent} from './print/print.component'; import {SampleComponent} from './sample/sample.component'; import {SettingsComponent} from './settings/settings.component'; +import {MultipleLabelsComponent} from './multiple-labels/multiple-labels.component'; export const routes: Routes = [ { @@ -32,6 +33,11 @@ export const routes: Routes = [ pathMatch: 'full', component: SettingsComponent }, + { + path: 'multiple', + pathMatch: 'full', + component: MultipleLabelsComponent + } ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0fe3a4c..ed6aaa2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -36,6 +36,7 @@ import {ApiService} from './services/api.service'; import {CacheService} from './services/cache.service'; import {SettingsService} from './services/settings.service'; import {SettingsComponent} from './settings/settings.component'; +import { MultipleLabelsComponent } from './multiple-labels/multiple-labels.component'; /** * This function is used internal to get a string instance of the `` value from `index.html`. @@ -62,6 +63,7 @@ export function getBaseHref(platformLocation: PlatformLocation): string { FooterComponent, LabelLayoutComponent, LoadingComponent, + MultipleLabelsComponent, NavbarComponent, PrintComponent, PrintLayoutComponent, diff --git a/src/app/config/defaults.ts b/src/app/config/defaults.ts index 560f993..ed37539 100644 --- a/src/app/config/defaults.ts +++ b/src/app/config/defaults.ts @@ -43,7 +43,7 @@ export const labelLayouts = { export const defaultOptions: AppDefaultsOptions = { barCodeNumLength: 9, // Number of digits in Bar Code. - barCodeRegExp: /^[\d]{14}$|^[\d]{9}$/, // Pattern for Bar Code data. + cardNumRegExp: /^[\d]{14}$|^[\d]{9}$/, // Pattern for Bar Code data. // Scanned barcodes will be either 9 or 14 digits long. // Manually-entered ID numbers will be exactly 9 digits long. countsCollection: 'counts', // Name of collection for Line Counts in Firebase. diff --git a/src/app/label-layout/formats/rectangle-3x1-code128/rectangle-3x1-code128.component.svg b/src/app/label-layout/formats/rectangle-3x1-code128/rectangle-3x1-code128.component.svg index 2900c6d..81668b2 100644 --- a/src/app/label-layout/formats/rectangle-3x1-code128/rectangle-3x1-code128.component.svg +++ b/src/app/label-layout/formats/rectangle-3x1-code128/rectangle-3x1-code128.component.svg @@ -9,12 +9,11 @@ > - - - - - + UP + + + @@ -30,16 +29,26 @@ y="0" rx="2" /> - - + + + + + [width]="50000" + transform="scale(.00035)"/> - {{sample.barcode}} + + #{{sample.student_id}} + {{sample.initials}} + + + {{sample.date | date:'yyyy-MM-dd HH:mm'}} + {{sample.location}} + + {{sample.barcode}} diff --git a/src/app/label-layout/label-layout.component.spec.ts b/src/app/label-layout/label-layout.component.spec.ts index 5c7aa80..e9dbb73 100644 --- a/src/app/label-layout/label-layout.component.spec.ts +++ b/src/app/label-layout/label-layout.component.spec.ts @@ -21,7 +21,7 @@ describe('LabelLayoutComponent', () => { fixture = TestBed.createComponent(LabelLayoutComponent); component = fixture.componentInstance; component.dateCreated = new Date(); - component.barCode = '123456789'; + component.cardNum = '123456789'; component.initials = 'abc'; fixture.detectChanges(); }); diff --git a/src/app/label-layout/label-layout.component.ts b/src/app/label-layout/label-layout.component.ts index 360d564..cf7967d 100644 --- a/src/app/label-layout/label-layout.component.ts +++ b/src/app/label-layout/label-layout.component.ts @@ -12,7 +12,7 @@ import {SettingsService} from '../services/settings.service'; }) export class LabelLayoutComponent implements OnInit { @Input() dateCreated: Date; - @Input() barCode: string; + @Input() cardNum: string; @Input() initials: string; settings: AppDefaults; sample: Sample; @@ -25,9 +25,9 @@ export class LabelLayoutComponent implements OnInit { ngOnInit() { this.sample = { barcode: '', - student_id: this.barCode, + student_id: this.cardNum, initials: this.initials, - date: new Date(), + date: this.dateCreated, location: this.settings.locationId, }; diff --git a/src/app/models/appDefaults.interface.ts b/src/app/models/appDefaults.interface.ts index 17838a4..84ae3d6 100644 --- a/src/app/models/appDefaults.interface.ts +++ b/src/app/models/appDefaults.interface.ts @@ -3,7 +3,7 @@ import {LabelLayout} from './labelLayout.interface'; export interface AppDefaultsOptions { barCodeNumLength?: number; - barCodeRegExp?: RegExp | string; + cardNumRegExp?: RegExp | string; countsCollection?: string; dateDisplayFormat?: string; dateEncodedFormat?: string; @@ -20,7 +20,7 @@ export interface AppDefaultsOptions { export class AppDefaults { barCodeNumLength: number; - barCodeRegExp: RegExp; + cardNumRegExp: RegExp; countsCollection: string; dateDisplayFormat: string; dateEncodedFormat: string; diff --git a/src/app/models/labelLayout.interface.ts b/src/app/models/labelLayout.interface.ts index e6da243..e4666a3 100644 --- a/src/app/models/labelLayout.interface.ts +++ b/src/app/models/labelLayout.interface.ts @@ -35,7 +35,7 @@ export class LabelLayout { get dimensions() { return { pageWidth: this._toUnits(this.pageWidth), - pageHeight: this._toUnits(this.pageHeight), + pageHeight: this._toUnits(this.pageHeight * this.numCopies), }; } diff --git a/src/app/multiple-labels/multiple-labels.component.html b/src/app/multiple-labels/multiple-labels.component.html new file mode 100644 index 0000000..bd6313d --- /dev/null +++ b/src/app/multiple-labels/multiple-labels.component.html @@ -0,0 +1,17 @@ +
+ +
diff --git a/src/app/multiple-labels/multiple-labels.component.scss b/src/app/multiple-labels/multiple-labels.component.scss new file mode 100644 index 0000000..36f2b97 --- /dev/null +++ b/src/app/multiple-labels/multiple-labels.component.scss @@ -0,0 +1,48 @@ +@media screen { + #media-print { display: none !important; } + #media-screen { + display: flex !important; + + } +} + +@media print { + @page { + margin: 0 !important; + padding: 0 !important; + } + + ::ng-deep html, + ::ng-deep body { + margin: 0 !important; + padding: 0 !important; + background-color: transparent; + overflow: hidden; + } + + ::ng-deep .no-print, + ::ng-deep mat-card, + ::ng-deep app-navbar, + ::ng-deep app-footer { + display: none; + } + + #media-print { + display: block !important; + margin: 0 !important; + padding: 0 !important; + background-color: blue; + + .print-layout { + display: grid !important; + background-color: transparent; + } + + .page-break { + page-break-before: always; + } + + } + #media-screen { display: none !important; } +} + diff --git a/src/app/multiple-labels/multiple-labels.component.spec.ts b/src/app/multiple-labels/multiple-labels.component.spec.ts new file mode 100644 index 0000000..ea65570 --- /dev/null +++ b/src/app/multiple-labels/multiple-labels.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MultipleLabelsComponent } from './multiple-labels.component'; + +describe('MultipleLabelsComponent', () => { + let component: MultipleLabelsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MultipleLabelsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MultipleLabelsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/multiple-labels/multiple-labels.component.ts b/src/app/multiple-labels/multiple-labels.component.ts new file mode 100644 index 0000000..4357456 --- /dev/null +++ b/src/app/multiple-labels/multiple-labels.component.ts @@ -0,0 +1,100 @@ +import {AfterViewChecked, AfterViewInit, Component, OnInit} from '@angular/core'; +import {Sample} from '../models/sample.interface'; +import {LabelLayout} from '../models/labelLayout.interface'; +import {SettingsService} from '../services/settings.service'; +import {AppDefaults} from '../models/appDefaults.interface'; +import {createQrCodeValue} from '../_util/qrCode'; + +@Component({ + selector: 'app-multiple-labels', + templateUrl: './multiple-labels.component.html', + styleUrls: ['./multiple-labels.component.scss'] +}) +export class MultipleLabelsComponent implements OnInit, AfterViewInit { + samples: Sample[]; + layout: LabelLayout; + settings: AppDefaults; + numLabels = 10; + + constructor(private settingsService: SettingsService) { + const startingId = 391; + this.settings = this.settingsService.getSettings(); + this.layout = new LabelLayout(this.settings.labelLayout); + this.layout.numCopies = this.numLabels; + + this.samples = Array(this.numLabels).fill('').map((x, i) => { + const idBase = (startingId + i).toString(10).padStart(3, '0'); + const studentId = idBase + idBase + idBase; + const compId = Array(3) + .fill('') + .map((y, j) => this.randomChar(j)) + .join('') + idBase; + const sample: Sample = { + barcode: '', + student_id: studentId, + initials: compId, + location: this.settings.locationId, + date: new Date(), + }; + + sample.barcode = createQrCodeValue( + sample.student_id, + sample.initials, + sample.date, + sample.location, + this.layout.delimiter, + this.layout.barcodeType + ); + + return sample; + }); + + console.log('samples', this.samples); + } + + get pagesToGridFractions(): string { + return this.samples.map(() => '1fr').join(' '); + } + + get pageHeight(): string { + return `${this.layout.pageHeight * this.numLabels}${this.layout.units}`; + } + + get pageWidth(): string { + return this.layout.dimensions.pageWidth; + } + + ngOnInit(): void { + } + + ngAfterViewInit() { + // Inject the print CSS, with dynamic layout settings, into the DOM. + const printCss = ` + @media print { + @page { + size: ${this.layout.dimensions.pageWidth} ${this.layout.pageHeight}${this.layout.units}; + } + + html, + body { + width: ${this.layout.dimensions.pageWidth} !important; + height: ${this.layout.dimensions.pageHeight} !important; + } + } + `; + const headEl = document.getElementsByTagName('head')[0]; + const styleEl = document.createElement('style'); + styleEl.setAttribute('type', 'text/css'); + styleEl.appendChild(document.createTextNode(printCss)); + headEl.appendChild(styleEl); + } + + private randomChar(i) { + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const numbers = '1234567890'; + + // If it's the first character, return letters only. + const from = (i === 0) ? letters : letters.concat(numbers); + return from.charAt(Math.floor(Math.random() * from.length)); + } +} diff --git a/src/app/print-layout/print-layout.component.html b/src/app/print-layout/print-layout.component.html index 7b914f4..1c654a5 100644 --- a/src/app/print-layout/print-layout.component.html +++ b/src/app/print-layout/print-layout.component.html @@ -13,7 +13,7 @@ > diff --git a/src/app/print-layout/print-layout.component.ts b/src/app/print-layout/print-layout.component.ts index d877f1c..09436cf 100644 --- a/src/app/print-layout/print-layout.component.ts +++ b/src/app/print-layout/print-layout.component.ts @@ -10,7 +10,7 @@ import {SettingsService} from '../services/settings.service'; }) export class PrintLayoutComponent implements AfterViewInit { @Input() dateCreated: Date; - @Input() barCode: string; + @Input() cardNum: string; @Input() initials: string; settings: AppDefaults; layout: LabelLayout; diff --git a/src/app/print/print.component.html b/src/app/print/print.component.html index e04e356..1055780 100644 --- a/src/app/print/print.component.html +++ b/src/app/print/print.component.html @@ -16,13 +16,14 @@ *ngIf="isSaved; else loadingMessage" >