JavaScript (JS) is an object-oriented programming language that allows engineers to produce and implement complex features within web browsers. JavaScript is popular because of its versatility and is preferred as the primary choice unless a specific function is needed. In this Zone, we provide resources that cover popular JS frameworks, server applications, supported data types, and other useful topics for a front-end engineer.
Hello! In this article, we will create a Temperature Anomaly chart. For this example, we will use the LightningCharts JS library, specifically the XY chart. For those interested in technical details, here are some of XY Chart characteristics: This type of chart works with only two axes, X and Y. It allows us to locate data points within the graph as coordinates. The XY chart is a great tool for measuring and reading temperature anomalies. Anomaly refers to a change from a reference or average value within an extended period of time. Positive values indicate that the temperature has been warmer, while negative values refer to a colder temperature. Main Uses of a Temperature Anomaly Chart Climate Monitoring: Temperature anomaly charts help monitor changes in temperature patterns over time, providing insights into climate trends and overall climate conditions. Climate Research: temperature anomaly charts have scientific use for studying climate dynamics, identifying climate drivers, and investigating the impact of natural and human-induced factors on temperature variations. Weather Forecasting: in meteorology, temperature anomaly charts are used to forecast weather patterns by identifying areas with unusual warmth or cold, contributing to more accurate short-term weather predictions. Environmental Analysis: Temperature anomalies affect ecosystems and environmental systems, making temperature anomaly charts valuable for analyzing the impacts of temperature variations on vegetation, wildlife, and ocean currents. Let's begin implementation. Template Setup 1. Please, download the following template (.ZIP), where you'll find the project and all required files to follow this tutorial. JavaScript Temperature Anomaly Chart Project Template. 2. Once you download the project, open it in Visual Studio, and visualize the following file tree: 3. Now, open a new terminal, and as usual in a NodeJS project, run the NPM install command. Refer to this article for more information on installing NodeJS. This would be everything for setting up the template. Now let's code. CHART.ts Inside this file, we will have all the logic needed to create our chart, configure animations, and format the data. 1. Importing the JSON data: JavaScript import data from './data.json' Ps. If you're new to JSON data, I recommend you this JSON data modeling guide. 2. Declare the constant lcjs that will refer to our @arction/lcjs library. 3. Extract required classes from lcjs. JavaScript const lcjs = require('@arction/lcjs') const { lightningChart, AxisTickStrategies, UIElementBuilders, UIOrigins, ImageFill, emptyLine, ImageFitMode, emptyFill, Themes } = lcjs 4. Creating the chart object: JavaScript const chart = lightningChart() .ChartXY({ theme: Themes.darkLime, }) .setTitle('') setTitle: Text that will be displayed as the top title in the dashboard. Theme: The LightningChart JS library features a collection of default implementations that can be accessed by Themes. The color theme of the components must be specified when it is created and can't be changed afterward. 5. Getting the Y axis: JavaScript const axisY1 = chart.getDefaultAxisY().setTitle('Atmospheric Carbon Dioxide (ppm)') The getDefaultAxisY, gives access to the Y axis, the same situation for the X axis (getDefaultAxisX). Once we have access to this, we can add UI properties like the title. AxisY1 corresponds to the meaning line in the chart: 6. Creating the main Y-Axis line: JavaScript const axisY2 = chart .addAxisY({ opposite: true, }) .setTitle('Temperature Anomaly Index (°C)') // Hide tick grid-lines from second Y axis. .setTickStrategy(AxisTickStrategies.Numeric, (ticks) => ticks .setMinorTickStyle((minor) => minor.setGridStrokeStyle(emptyLine)) .setMajorTickStyle((major) => major.setGridStrokeStyle(emptyLine)), ) The tick strategy defines the positioning and formatting logic of Axis ticks as well as the style of created ticks. The numeric property corresponds to the number values to be shown on the Y axis. For XY, the ticks will be empty lines because we don’t need to show each tick in the chart: JavaScript major.setGridStrokeStyle((solidLine) => solidLine.setThickness(500))), 7. X-axis: JavaScript const axisX = chart.getDefaultAxisX().setTickStrategy(AxisTickStrategies.DateTime) As you can see, the X axis has the Date Time strategy because we need to show the data by date: 8. Using the JSON data: JavaScript const { temperature, co2 } = data //code stored in "data.json" { "temperature":[-0.16,-0.08,-0.1,-0.17,-0.28,-0.33,-0.31,-0.36,-0.17,-0.1,-0.35,-0.22,-0.27,-0.31,-0.3,-0.23,-0.11,-0.11,-0.27,-0.18,-0.08,-0.16,-0.28,-0.37,-0.47,-0.26,-0.22,-0.39,-0.43,-0.49,-0.44,-0.44,-0.36,-0.35,-0.16,-0.15,-0.36,-0.46,-0.3,-0.28,-0.28,-0.19,-0.29,-0.27,-0.27,-0.22,-0.11,-0.22,-0.2,-0.36,-0.16,-0.1,-0.16,-0.29,-0.13,-0.2,-0.15,-0.03,0,-0.02,0.13,0.19,0.07,0.09,0.2,0.09,-0.07,-0.03,-0.11,-0.11,-0.17,-0.07,0.01,0.08,-0.13,-0.14,-0.19,0.05,0.06,0.03,-0.03,0.06,0.03,0.05,-0.2,-0.11,-0.06,-0.02,-0.08,0.05,0.03,-0.08,0.01,0.16,-0.07,-0.01,-0.1,0.18,0.07,0.16,0.26,0.32,0.14,0.31,0.16,0.12,0.18,0.32,0.39,0.27,0.45,0.4,0.22,0.23,0.32,0.45,0.33,0.46,0.61,0.38,0.39,0.54,0.63,0.62,0.53,0.68,0.64,0.66,0.54,0.66,0.72,0.61,0.65,0.68,0.74,0.9,1.01,0.92,0.85,0.98,1.02], "co2":[285.2,285.1,285,285,284.9,285.1,285.4,285.6,285.9,286.1,286.4,286.6,286.7,286.8,286.9,287.1,287.2,287.3,287.4,287.5,287.7,287.9,288,288.2,288.4,288.6,288.7,288.9,289.5,290.1,290.8,291.4,292,292.5,292.9,293.3,293.8,294,294.1,294.2,294.4,294.6,294.8,294.7,294.8,294.8,294.9,294.9,294.9,295.3,295.7,296.2,296.6,297,297.5,298,298.4,298.8,299.3,299.7,300.1,300.6,301,301.3,301.4,301.6,302,302.4,302.8,303,303.4,303.7,304.1,304.5,304.9,305.3,305.8,306.2,306.6,307.2,307.5,308,308.3,308.9,309.3,309.7,310.1,310.6,311,311.2,311.3,311,310.7,310.5,310.2,310.3,310.3,310.4,310.5,310.9,311.3,311.8,312.2,312.6,313.2,313.7,314.3,314.8,315.34,316.18,317.07,317.73,318.43,319.08,319.65,320.23,321.59,322.31,323.04,324.23,325.54,326.42,327.45,329.43,330.21,331.36,331.92,333.73,335.42,337.1,338.99,340.36,341.57,342.53,344.24,345.72,347.15,348.93,351.47,353.15,354.29] } 9. Adding values to the Atmospheric Carbon Dioxide line: JavaScript const carbonDioxideSeries = chart .addLineSeries({ yAxis: axisY1, }) .setName('Atmospheric Carbon Dioxide (ppm)') // Data set contains PPM measurement values only. First measurement is from year 1880, and each consecutive measurement is 1 year after previous. .add( co2.map((ppm, i) => ({ y: ppm, x: new Date(1880 + i, 0, 1, 0, 0, 0, 0).getTime(), })), ) As you can see, a line series was added to the Y1 axis. The name was set to the series, and the data was mapped from the CO2 array object from the JSON data. We don't have a date value, so just add 1 consecutive year from 1880, to create a non-breaking line. 10. Adding the legend box: JavaScript // Add legend. const legend = chart.addLegendBox(undefined, { x: axisX, y: axisY1 }).add(chart) .setOrigin(UIOrigins.LeftTop) .setMargin(4) const positionLegendOnAxes = () => legend.setPosition({ x: axisX.getInterval().start, y: axisY1.getInterval().end }) positionLegendOnAxes() The legendbox is added to the meaning line (Y axis) and the unique X axis. setOrigin: Sets the position origin of this UiElement. setMargin: Set margin around the object in pixels. setPosition: Sets the position of this UiElement relative to its origin. getInterval: Get the currently applied axis scale interval. NPM Start For initializing the chart, open up a new terminal and run the npm start command. You'll see the local host path. Click on it and interact with the chart on your browser. And here, you can visualize the final chart: Conclusion In this article, we have created an implementation of an XY chart in a very simple way. In order to generate an XY chart, we just need to make use of the charXY() method. In this example, we have used the default values within the LC JS library, but in your case, you would only need an object of type JSON with the values that you need. Technically, the process should work as long as you use two arrays. Almost all of the code development was focused on chart customization, and the process how to load the data is quite easy to understand. Another important point is the use of the theme catalog, which allows us to generate a chart with a fairly complex appearance, but without the need to resort to external CSS classes. Definitely, LC JS helps to perform optimal jobs for less use of time performed. In case you have any questions, leave a comment with any code snippets, and I'll be happy to help! See you in the next article with more Data Visualization tutorials, Bye :)
Angular unit testing checks isolated pieces of code in an Angular app. It allows users to add new features without interrupting any other part of their application. Jasmine is a JavaScript testing framework, and Karma is a node-based testing tool for JavaScript codes across multiple real browsers. This blog helps you get started with Angular unit testing leveraging Karma and Jasmine. Introduction to Angular Unit Testing First things first, you must have Angular installed on your machine. That is where you need to start Angular installation. If you already have Angular installed, feel free to skip the next step. Creating and managing an Angular project is quite easy. There are various competing libraries, frameworks, and tools that can resolve the problems. The Angular team has created Angular CLI, which is a command-line tool used to streamline your Angular projects. Angular CLI is installed via npm, so you are going to need to have Node installed on your machine. After Node is installed, you can run the following command in your terminal. The time it takes for installation to complete may change. After it is done, you can see Angular CLI’s version by typing the following command in your terminal. Now that you have Angular CLI installed, you are ready to create an Angular sample app. Run the following command in your terminal. ng new angular-unit-test-application After executing the command, you will be asked whether you want to add Angular routing. Type Y and press ENTER. You will then be asked to choose between several options of stylesheet formats for your app. It will take a few minutes, and once done; you will move to your testing app. Unit testing tests the isolated units of code. Unit tests aim to answer questions such as, Has the sort function ordered the list in the correct order? Was I able to think about the logic correctly? To answer these questions, it is critical to isolate the unit of code under test. That is because when you are testing the sort function, you don't want to be forced into creating related pieces, such as making any API calls to fetch the actual database data to sort. You are already aware that unit testing tests individual components of the software or app. The main motive behind this is to check that all the individual parts are working as intended. A unit is the smallest possible component of software that can be tested. Usually, it has several inputs and one output. Let’s jump into the Angular web app testing part. Run the following command in your terminal. After waiting for a few seconds, you will see a new window of your web browser open on a page looking like this, as you see below. Deciphering the Role of Karma and Jasmine in Angular Unit Testing What Is Karma Test Runner? Karma is a testing automation tool developed by the Angular JS team as it was getting difficult to test their own framework features with current tools. As a result, they developed Karma and transitioned it to Angular as the default test runner for apps developed with the Angular CLI. Apart from getting along with Angular, it offers flexibility to tailor Karma to your workflow. It has the option to test your code across different browsers and devices like tablets, phones, etc. Karma gives you options to substitute Jasmine with other testing frameworks like Mocha and QUnit. Here is the content of the karma.conf.js file in a sample project. What is Jasmine? Jasmine is a free as well as an open-source Behavior Driven Development (BDD) framework that tests JavaScript code and also goes well with Karma. Like Karma, it is also the suggested testing framework within the Angular documentation. The flow of how the test run looks like, Testing component add-organization.component.ts JavaScript import { TranslateService } from '@ngx-translate/core'; import { SharedService } from 'src/app/shared/services/shared.service'; import { Component, OnInit } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { appConstants, allowedFileFormat, AppRoutes } from 'src/app/app.constants'; import { SelectOption } from 'src/app/shared/interface/select-option'; import { OrganizationService } from '../organization.service'; import { getOrganizations } from 'src/app/shared/config/api'; import { Router, ActivatedRoute } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { ConfirmDialogComponent } from 'src/app/shared/components/confirm-dialog/confirm-dialog.component'; @Component({ selector: 'app-add-organization', templateUrl: './add-organization.component.html', styleUrls: ['./add-organization.component.scss'] }) export class AddOrganizationComponent implements OnInit { orgId: any; submitted = false; logoFileFormat = allowedFileFormat.logoFileFormat; logoFileSize = allowedFileFormat.logoFileSize; selectedImageLogo!: File; selectedImageLogoUrl = ''; countryOptions: SelectOption[] = []; menuList = [{ label: 'HOME', url: '' }, { label: 'ORGANIZATION', url: '/app/organization' }, { label: 'CREATE ORGANIZATION', url: '' }]; themeData: any; configurationTabStatus = true; loading = false; userData = [{name: 'name'}]; userCount = 15; undefinedVariable: any; currentStep = 1; completedSteps: number[] = []; orgForm = this.createForm(); constructor(private fb: FormBuilder, public sharedService: SharedService, public translateService: TranslateService, private route: Router, public organizationService: OrganizationService, private activatedRoute: ActivatedRoute, private confirmDialog: MatDialog) { } ngOnInit(): void { this.orgId = this.activatedRoute.snapshot.params['orgId']; if (this.orgId) { this.configurationTabStatus = false; } this.getCountries(); this.getOrganizationDetails(); } createForm() { return this.fb.group({ firstName: ['', [Validators.required, Validators.maxLength(200)]], lastName: ['', [Validators.required, Validators.maxLength(200)]], isActive: [true], email: ['', [Validators.required, Validators.email, Validators.maxLength(200)]], }); } get formControl() { return this.orgForm.controls; } isFieldInvalid(field: string) { return ( (this.formControl[field].invalid && this.formControl[field].touched) || (this.formControl[field].untouched && this.submitted && this.formControl[field].invalid) ); } displayFieldCss(field: string) { return { [appConstants.default.hasErrorClass]: this.isFieldInvalid(field), }; } onViewUser() { if (this.orgId) { this.sharedService.setOrganizationId(this.orgId); this.route.navigate([AppRoutes.userPath]); this.userData = []; this.submitted = false; this.userCount = 10; this.undefinedVariable = undefined; } else { this.route.navigate([AppRoutes.addUser]); this.userData = [{name: 'ZYMR'}]; this.submitted = true; this.userCount = 20; this.undefinedVariable = 'Test'; } } isCompleted = (step: any) => this.completedSteps.indexOf(step) !== -1; navigateToStep(step: any) { if(this.currentStep !== step && (this.orgId || this.isCompleted(step))) { switch (step) { case 1: this.route.navigate([AppRoutes.user + this.orgId]); break; case 2: this.route.navigate([AppRoutes.organization + this.orgId]); break; case 3: this.route.navigate([AppRoutes.userPath + this.orgId]); break; case 4: this.route.navigate([AppRoutes.addUser + this.orgId]); break; default: break; } } } changeOrgStatus(event: any) { if (this.orgId && !event.checked) { const confirmDialog = this.confirmDialog.open(ConfirmDialogComponent, { disableClose: true, data: { title: this.translateService.instant('COMMON.ACTION_CONFIRM.TITLE'), message: this.translateService.instant('ORGANIZATIONS.ORGANIZATIONS_DEACTIVE'), }, maxWidth: '100vw', width: '600px', }); if (confirmDialog) { confirmDialog.afterClosed().subscribe(result => { if (result === true) { this.formControl['isActive'].setValue(event.checked); } else { this.formControl['isActive'].setValue(!event.checked); } }); } } } onSubmit(): void { const formData = new FormData(); formData.append('firstName', this.formControl['firstName'].value); formData.append('lastName', this.formControl['lastName'].value); formData.append('isActive', this.formControl['isActive'].value); formData.append('email', this.formControl['email'].value); if (this.orgId) { formData.append('Id', this.orgId); this.createEditNewOrganization(formData, appConstants.methodType.put); } else { this.createEditNewOrganization(formData, appConstants.methodType.post); } } private createEditNewOrganization(formData: FormData, methodType: string): void { this.submitted = true; if (this.orgForm.invalid) { return; } this.sharedService.showLoader(); this.organizationService.postFile(getOrganizations, formData, methodType).subscribe({ next: (res: any) => { this.sharedService.responseHandler(res, true, true, true); if (this.sharedService.isApiSuccess(res)) { this.orgId = res.data; if (methodType === appConstants.methodType.post) { this.route.navigate([AppRoutes.editOrganization + '/' + this.orgId]); } else { this.getOrganizationDetails(); } } }, error: (err: any) => { this.sharedService.errorHandler(err); }, complete: () => this.sharedService.hideLoader() } ); } private getOrganizationDetails() { if (this.orgId) { this.loading = true; const apiUrl = getOrganizations + '/' + this.orgId; this.sharedService.showLoader(); this.organizationService.get(apiUrl).subscribe({ next: (res: any) => { if (this.sharedService.isApiSuccess(res)) { this.configurationTabStatus = false; this.selectedImageLogoUrl = res.data.imageURL ? (res.data.imageURL + '?modifiedDate=' + res.data.modifiedDate) : res.data.imageURL; const formattedData = this.organizationService.prepareOrganizationDetailsResponse(res.data); this.orgForm.patchValue(formattedData); } }, error: (err: any) => { this.sharedService.errorHandler(err); }, complete: () => { this.loading = false; this.sharedService.hideLoader(); } } ); } } private getCountries(): void { this.sharedService.getCountriesList().subscribe( (res: Array<SelectOption>) => { this.countryOptions = res; } ); } } add-organization.component.spec.ts JavaScript import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { AddOrganizationComponent } from './add-organization.component'; import { HttpClientModule } from '@angular/common/http'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule, TranslateLoader, TranslateFakeLoader } from '@ngx-translate/core'; import { ToastrModule } from 'ngx-toastr'; import { SharedModule } from 'src/app/shared/modules/shared.module'; import { OrganizationService } from '../organization.service'; import { appConstants, AppRoutes } from 'src/app/app.constants'; import { defer } from 'rxjs'; describe('AddOrganizationComponent', () => { let component: AddOrganizationComponent; let fixture: ComponentFixture<AddOrganizationComponent>; beforeEach(() => { TestBed.configureTestingModule({ imports: [ HttpClientModule, RouterTestingModule, SharedModule, ToastrModule.forRoot(), TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }) ], declarations: [AddOrganizationComponent], providers: [ OrganizationService ] }).compileComponents(); fixture = TestBed.createComponent(AddOrganizationComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeTruthy(); }); it('should get organization Form', () => { component.createForm(); expect(component.formControl).not.toBeNull(); }); it('should page is in edit mode', () => { (component as any).activatedRoute = { snapshot: { params: { orgId: '123' } } }; spyOn((component as any), 'getCountries'); spyOn((component as any), 'getOrganizationDetails'); component.orgId = '123'; component.ngOnInit(); expect(component.configurationTabStatus).toBeFalsy(); }); it('should initialize country dropdown', waitForAsync(() => { const countryList = [{ value: 1, display: 'india', }]; spyOn((component as any).sharedService, 'getCountriesList').and.returnValue(promiseData(countryList)); (component as any).getCountries(); fixture.whenStable().then(() => { expect(component.countryOptions).toEqual(countryList); }); })); it('should be toggled to deactivated organization', waitForAsync(() => { component.orgId = '123'; component.createForm(); spyOn((component as any).confirmDialog, 'open').and.returnValue({ afterClosed: () => promiseData(true) }); component.changeOrgStatus({ checked: false }); fixture.whenStable().then(() => { expect(component.formControl['isActive'].value).toBeFalsy(); }); })); it('should be toggled activated organization', waitForAsync(() => { component.orgId = '123'; component.createForm(); spyOn((component as any).confirmDialog, 'open').and.returnValue({ afterClosed: () => promiseData(false) }); component.changeOrgStatus({ checked: false }); fixture.whenStable().then(() => { expect(component.formControl['isActive'].value).toBeTruthy(); }); })); it('should save organization details', () => { component.orgId = ''; const spy = spyOn((component as any), 'createEditNewOrganization'); component.onSubmit(); expect(spy).toHaveBeenCalled(); }); it('should update organization details', () => { component.orgId = '123'; const spy = spyOn((component as any), 'createEditNewOrganization'); component.onSubmit(); expect(spy).toHaveBeenCalled(); }); it('should save organization data on createEditNewOrganization call', waitForAsync(() => { component.createForm(); component.orgForm.patchValue({ lastName: 'name', firstName: 'vatNumber', email: 'test@gmail.com', }); spyOn((component as any).organizationService, 'postFile').and.returnValue(promiseData({ code: '', data: '123', message: '', status: appConstants.responseStatus.success })); const spy = spyOn((component as any).sharedService, 'showLoader'); const spyResponseHandler = spyOn((component as any).sharedService, 'responseHandler'); const navigateByUrlSpy = spyOn((component as any).route, 'navigateByUrl'); (component as any).createEditNewOrganization({}, appConstants.methodType.post); fixture.whenStable().then(() => { expect(spy).toHaveBeenCalled(); expect(spyResponseHandler).toHaveBeenCalled(); expect(navigateByUrlSpy).toHaveBeenCalled(); }); })); it('should update organization data on createEditNewOrganization call', waitForAsync(() => { component.createForm(); component.orgForm.patchValue({ lastName: 'name', firstName: 'vatNumber', email: 'test@gmail.com', }); spyOn((component as any).organizationService, 'postFile').and.returnValue(promiseData({ code: '', data: '123', message: '', status: appConstants.responseStatus.success })); const spy = spyOn((component as any).sharedService, 'showLoader'); const getOrganizationDetails = spyOn((component as any), 'getOrganizationDetails'); const spyResponseHandler = spyOn((component as any).sharedService, 'responseHandler'); (component as any).createEditNewOrganization({}, appConstants.methodType.put); fixture.whenStable().then(() => { expect(spy).toHaveBeenCalled(); expect(getOrganizationDetails).toHaveBeenCalled(); expect(spyResponseHandler).toHaveBeenCalled(); }); })); it('should org form invalid on createEditNewOrganization call', () => { component.createForm(); component.orgForm.patchValue({ name: 'name', }); (component as any).createEditNewOrganization({}, appConstants.methodType.post); expect(component.submitted).toBeTruthy(); }); it('should handle error while saving organization data on createEditNewOrganization call', waitForAsync(() => { component.createForm(); component.orgForm.patchValue({ lastName: 'name', firstName: 'vatNumber', email: 'test@gmail.com', }); spyOn((component as any).organizationService, 'postFile').and.returnValue(rejectPromise({ code: '', data: '', message: '', status: appConstants.responseStatus.error })); const spyResponseHandler = spyOn((component as any).sharedService, 'errorHandler'); (component as any).createEditNewOrganization({}, appConstants.methodType.post); fixture.whenStable().then(() => { expect(spyResponseHandler).toHaveBeenCalled(); }); })); it('should get organization details on getOrganizationDetails call', waitForAsync(() => { component.createForm(); component.orgId = '123'; const orgDetails = { lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', isActive: true }; spyOn((component as any).organizationService, 'get').and.returnValue(promiseData({ code: '', data: { lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', imageURL: 'http://www.test.com/img1', modifiedDate: '12-12-12', isActive: true }, status: appConstants.responseStatus.success, message: '' })); spyOn(component.sharedService, 'isApiSuccess').and.returnValue(true); spyOn(component.organizationService, 'prepareOrganizationDetailsResponse').and.returnValue(orgDetails); (component as any).getOrganizationDetails(); fixture.whenStable().then(() => { expect(component.selectedImageLogoUrl).toEqual('http://www.test.com/img1?modifiedDate=12-12-12'); expect(component.configurationTabStatus).toBeFalsy(); expect(component.orgForm.value).toEqual({ lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', isActive: true, }); }); })); it('should get organization details but imageUrl is empty on getOrganizationDetails call', waitForAsync(() => { component.createForm(); component.orgId = '123'; const orgDetails = { lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', isActive: true }; spyOn((component as any).organizationService, 'get').and.returnValue(promiseData({ code: '', data: { lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', imageURL: '', modifiedDate: '', isActive: true }, status: appConstants.responseStatus.success, message: '' })); spyOn(component.sharedService, 'isApiSuccess').and.returnValue(true); spyOn(component.organizationService, 'prepareOrganizationDetailsResponse').and.returnValue(orgDetails); (component as any).getOrganizationDetails(); fixture.whenStable().then(() => { expect(component.selectedImageLogoUrl).toEqual(''); expect(component.configurationTabStatus).toBeFalsy(); expect(component.orgForm.value).toEqual({ lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', isActive: true, }); }); })); it('should handle error while getting organization details on getOrganizationDetails call', waitForAsync(() => { component.createForm(); component.orgId = '123'; spyOn((component as any).organizationService, 'get').and.returnValue(rejectPromise({ code: '', data: {}, status: appConstants.responseStatus.error, message: '' })); const spy = spyOn(component.sharedService, 'errorHandler'); (component as any).getOrganizationDetails(); fixture.whenStable().then(() => { expect(spy).toHaveBeenCalled(); }); })); it('should return class on displayFieldCss', () => { component.createForm(); component.orgForm.controls['email'].setValue('invalid_email@@dotcom'); component.submitted = true; expect(component.displayFieldCss('email')).toEqual({ 'has-error': true }); }); it('should set organization id and navigate to user list page', () => { component.orgId = '123'; const spy = spyOn(component.sharedService, 'setOrganizationId'); const navigateSpy = spyOn((component as any).route, 'navigate'); component.onViewUser(); expect(spy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalled(); expect(component.userData.length).toEqual(0); expect(component.submitted).toBeFalsy(); expect(component.userCount).toBeLessThan(15); expect(component.undefinedVariable).toBeUndefined(); }); it('should navigate to add user page', () => { const navigateSpy = spyOn((component as any).route, 'navigate'); component.onViewUser(); expect(navigateSpy).toHaveBeenCalled(); expect(component.userData.length).toEqual(1); expect(component.userData).toEqual([{name: 'ZYMR'}]); expect(component.submitted).toBeTruthy(); expect(component.userCount).toBeGreaterThan(15); expect(component.undefinedVariable).toBeDefined(); }); describe('on step click', () => { let spyRoute: jasmine.Spy<any>; beforeEach(waitForAsync(() => { spyRoute = spyOn((component as any).route, 'navigate'); })); it('should be navigate to first main info step with event id', () => { component.completedSteps = [1,2]; component.currentStep = 2; component.orgId = '10'; component.navigateToStep(1); expect(spyRoute).toHaveBeenCalledWith([AppRoutes.user + component.orgId]); }); it('should be navigate to second event detail step with event id', () => { component.completedSteps = [1,2]; component.currentStep = 1; component.orgId = '10'; component.navigateToStep(2); expect(spyRoute).toHaveBeenCalledWith([AppRoutes.organization + component.orgId]); }); it('should be navigate to third particiant step with event id', () => { component.completedSteps = [1,2,3]; component.currentStep = 1; component.orgId = '10'; component.navigateToStep(3); expect(spyRoute).toHaveBeenCalledWith([AppRoutes.userPath + component.orgId]); }); it('should be navigate to fourth communication step with event id', () => { component.completedSteps = [1,2,3,4]; component.currentStep = 3; component.orgId = '10'; component.navigateToStep(4); expect(spyRoute).toHaveBeenCalledWith([AppRoutes.addUser + component.orgId]); }); it('should not navigate to any step', () => { component.completedSteps = [1,2,3,4,5]; component.currentStep = 3; component.orgId = null; component.navigateToStep(5); expect(spyRoute).not.toHaveBeenCalled(); }); }); }); export const rejectPromise = (msg: any) => defer(() => Promise.reject(msg)); export const promiseData = <T>(data: T) => defer(() => Promise.resolve(data)); So, in this component, you can see you have a test list of things which are listed below: 2. Angular reactive form validation 3. Conditional variable and routing calls 4. Multiple cases to cover branches and statement 5. Pop up modal with closing with yes and no 6. Submitting form data and calling the post API and also the error handling part will cover 7. Get organization details using the API calls So all of these things, you will test in our spec file. You can see in the above image it will cover all the statements, functions, branches, and all. There will be multiple things you will notice from the above code snippet. What each of these does is explained below: We use a describe to begin our test, and we gave the name of the component that we are testing inside it. we can execute some pieces of code before the execution of each spec. This functionality is beneficial for running the common code in the app. Inside the beforeEach, we have TestBed.ConfigureTestingModule. TestBed sets up the configurations and initializes the environment suitable for our test.ConfigureTestingModule sets up the module that allows us to test our component. You can say that it creates a module for our test environment and have declarations, imports, and providers inside it. Testing Service 1. Organization.service.ts JavaScript import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { HttpBaseService } from '../shared/services/httpbase/httpbase.service'; import { LanguageService } from '../shared/services/language/language.service'; @Injectable({ providedIn: 'root' }) export class OrganizationService extends HttpBaseService { constructor(httpClient: HttpClient, languageService: LanguageService) { super(httpClient, languageService); } prepareOrganizationListResponse(resList: any[]) { let organizationList: any = []; let organization: any = {}; resList.forEach(list => { organization.lastName = list.lastName, organization.firstName = list.firstName, organization.email = list.email, organization.isActive = list.isActive organizationList.push(organization); }); return organizationList; } prepareOrganizationDetailsResponse(res: any) { return { lastName: res.lastName, firstName: res.firstName, email: res.email, isActive: res.isActive }; } } 2. Organization.service.spec.ts JavaScript import { SharedModule } from 'src/app/shared/modules/shared.module'; import { HttpClientModule } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { TranslateModule, TranslateLoader, TranslateFakeLoader, TranslateService } from '@ngx-translate/core'; import { HttpBaseService } from '../shared/services/httpbase/httpbase.service'; import { SharedService } from '../shared/services/shared.service'; import { OrganizationService } from './organization.service'; import { OrganizationConfigurationApi, OrganizationListItemUI } from './organization.model'; describe('OrganizationService', () => { let service: OrganizationService; beforeEach(() => { TestBed.configureTestingModule({ imports: [ HttpClientModule, SharedModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), ], providers: [ TranslateService, HttpBaseService, SharedService, { provide: MAT_DIALOG_DATA, useValue: {} }, { provide: MatDialogRef, useValue: {} } ] }).compileComponents(); service = TestBed.inject(OrganizationService); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should be return properly formatted organization list response', () => { let organization: any = {}; organization.lastName = 'lastName', organization.firstName = 'firstName', organization.email = 'test@gmail.com', organization.isActive = true, expect(service.prepareOrganizationListResponse( [ { lastName: 'lastName', firstName: 'firstName', email: 'test@gmail.com', isActive: true, } ] )).toEqual([organization]); }); it('should be return organization details response', () => { expect(service.prepareOrganizationDetailsResponse({ lastName: 'lastName',
Developing scalable and reliable applications is a labor of love. A cloud-native system might consist of unit tests, integration tests, build tests, and a full pipeline for building and deploying applications at the click of a button. A number of intermediary steps might be required to ship a robust product. With distributed and containerized applications flooding the market, so too have container orchestration tools like Kubernetes. Kubernetes allows us to build distributed applications across a cluster of nodes, with fault tolerance, self-healing, and load balancing — plus many other features. Let’s explore some of these tools by building a distributed to-do list application in Node.js, backed by the YugabyteDB distributed SQL database. Getting Started A production deployment will likely involve setting up a full CI/CD pipeline to push containerized builds to the Google Container Registry to run on Google Kubernetes Engine or similar cloud services. For demonstration purposes, let’s focus on running a similar stack locally. We’ll develop a simple Node.js server, which is built as a docker image to run on Kubernetes on our machines. We’ll use this Node.js server to connect to a YugabyteDB distributed SQL cluster and return records from a rest endpoint. Installing Dependencies We begin by installing some dependencies for building and running our application. Docker Desktop Docker is used to build container images, which we’ll host locally. Minikube Creates a local Kubernetes cluster for running our distributed and application YugabyteDB Managed Next, we create a YugabyteDB Managed account and spin up a cluster in the cloud. YugabyteDB is PostgreSQL-compatible, so you can also run a PostgreSQL database elsewhere or run YugabyteDB locally if desired. For high availability, I’ve created a 3-node database cluster running on AWS, but for demonstration purposes, a free single-node cluster works fine. Seeding Our Database Once our database is up and running in the cloud, it’s time to create some tables and records. YugabyteDB Managed has a cloud shell that can be used to connect via the web browser, but I’ve chosen to use the YugabyteDB client shell on my local machine. Before connecting, we need to download the root certificate from the cloud console. I’ve created a SQL script to use to create a todos table and some records. SQL CREATE TYPE todo_status AS ENUM ('complete', 'in-progress', 'incomplete'); CREATE TABLE todos ( id serial PRIMARY KEY, description varchar(255), status todo_status ); INSERT INTO todos (description, status) VALUES ( 'Learn how to connect services with Kuberenetes', 'incomplete' ), ( 'Build container images with Docker', 'incomplete' ), ( 'Provision multi-region distributed SQL database', 'incomplete' ); We can use this script to seed our database. Shell > ./ysqlsh "user=admin \ host=<DATABASE_HOST> \ sslmode=verify-full \ sslrootcert=$PWD/root.crt" -f db.sql With our database seeded, we’re ready to connect to it via Node.js. Build a Node.js Server It’s simple to connect to our database with the node-postgres driver. YugabyteDB has built on top of this library with the YugabyteDB Node.js Smart Driver, which comes with additional features that unlock the powers of distributed SQL, including load-balancing and topology awareness. Shell > npm install express > npm install @yugabytedb/pg JavaScript const express = require("express"); const App = express(); const { Pool } = require("@yugabytedb/pg"); const fs = require("fs"); let config = { user: "admin", host: "<DATABASE_HOST>", password: "<DATABASE_PASSWORD>", port: 5433, database: "yugabyte", min: 5, max: 10, idleTimeoutMillis: 5000, connectionTimeoutMillis: 5000, ssl: { rejectUnauthorized: true, ca: fs.readFileSync("./root.crt").toString(), servername: "<DATABASE_HOST>", }, }; const pool = new Pool(config); App.get("/todos", async (req, res) => { try { const data = await pool.query("select * from todos"); res.json({ status: "OK", data: data?.rows }); } catch (e) { console.log("error in selecting todos from db", e); res.status(400).json({ error: e }); } }); App.listen(8000, () => { console.log("App listening on port 8000"); }); Containerizing Our Node.js Application To run our Node.js application in Kubernetes, we first need to build a container image. Create a Dockerfile in the same directory. Dockerfile FROM node:latest WORKDIR /app COPY . . RUN npm install EXPOSE 8000 ENTRYPOINT [ "npm", "start" ] All of our server dependencies will be built into the container image. To run our application using the npm start command, update your package.json file with the start script. JSON … "scripts": { "start": "node index.js" } … Now, we’re ready to build our image with Docker. Shell > docker build -t todo-list-app . Sending build context to Docker daemon 458.4MB Step 1/6 : FROM node:latest ---> 344462c86129 Step 2/6 : WORKDIR /app ---> Using cache ---> 49f210e25bbb Step 3/6 : COPY . . ---> Using cache ---> 1af02b568d4f Step 4/6 : RUN npm install ---> Using cache ---> d14416ffcdd4 Step 5/6 : EXPOSE 8000 ---> Using cache ---> e0524327827e Step 6/6 : ENTRYPOINT [ "npm", "start" ] ---> Using cache ---> 09e7c61855b2 Successfully built 09e7c61855b2 Successfully tagged todo-list-app:latest Our application is now packaged and ready to run in Kubernetes. Running Kubernetes Locally With Minikube To run a Kubernetes environment locally, we’ll run Minikube, which creates a Kubernetes cluster inside of a Docker container running on our machine. Shell > minikube start That was easy! Now we can use the kubectl command-line tool to deploy our application from a Kubernetes configuration file. Deploying to Kubernetes First, we create a configuration file called kubeConfig.yaml which will define the components of our cluster. Kubernetes deployments are used to keep pods running and up-to-date. Here we’re creating a cluster of nodes running the todo-app container that we’ve already built with Docker. YAML apiVersion: apps/v1 kind: Deployment metadata: name: todo-app-deployment labels: app: todo-app spec: selector: matchLabels: app: todo-app replicas: 3 template: metadata: labels: app: todo-app spec: containers: - name: todo-server image: todo ports: - containerPort: 8000 imagePullPolicy: Never In the same file, we’ll create a Kubernetes service, which is used to set the networking rules for your application and expose it to clients. YAML --- apiVersion: v1 kind: Service metadata: name: todo-app-service spec: type: NodePort selector: app: todo-app ports: - name: todo-app-service-port protocol: TCP port: 8000 targetPort: 8000 nodePort: 30100 Let’s use our configuration file to create our todo-app-deployment and todo-app-service. This will create a networked cluster, resilient to failures and orchestrated by Kubernetes! Shell > kubectl create -f kubeConfig.yaml Accessing Our Application in Minikube Shell > minikube service todo-app-service --url Starting tunnel for service todo-app-service. Because you are using a Docker driver on darwin, the terminal needs to be open to run it. We can find the tunnel port by executing the following command. Shell > ps -ef | grep docker@127.0.0.1 503 2363 2349 0 9:34PM ttys003 0:00.01 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -N docker@127.0.0.1 -p 53664 -i /Users/bhoyer/.minikube/machines/minikube/id_rsa -L 63650:10.107.158.206:8000 The output indicates that our tunnel is running at port 63650. We can access our /todos endpoint via this URL in the browser or via a client. Shell > curl -X GET http://127.0.0.1:63650/todos -H 'Content-Type: application/json' {"status":"OK","data":[{"id":1,"description":"Learn how to connect services with Kuberenetes","status":"incomplete"},{"id":2,"description":"Build container images with Docker","status":"incomplete"},{"id":3,"description":"Provision multi-region distributed SQL database","status":"incomplete"}]} Wrapping Up With a distributed infrastructure in place in our application and database tiers, we’ve developed a system built to scale and survive. I know, I know, I promised you the most resilient to-do app the world has ever seen and didn’t provide a user interface. Well, that’s your job! Extend the API service we’ve developed in Node.js to serve the HTML required to display our list. Look out for more from me on Node.js and distributed SQL — until then, keep on coding!
React, a popular JavaScript library for building user interfaces, has gained widespread adoption among web developers due to its flexibility, performance, and reusability. As React applications become more complex, having the right tools in your arsenal can greatly enhance your productivity and streamline your development workflow. In this article, we will explore the six best tools for React development that can help you build better, faster, and more efficient React applications. React DevTools React DevTools is an essential tool for React developers that allows you to inspect, debug, and profile React components in real time. It provides a set of powerful features, such as a component tree view, props and state inspection, a time-traveling debugger, and performance profiling, which can greatly aid in understanding and optimizing the behavior of your React applications. With React DevTools, you can gain insights into how your components are rendered, identify performance bottlenecks, and troubleshoot issues more effectively. Top Features of React DevTools Component Tree View: You can visualize the hierarchical structure of your React components. You can inspect the props and state of each component, view the component's rendered output, and even modify props and state in real time to see how it affects the component's behavior. Props and State Inspection: You’ll get detailed information about the props and state of each component, making it easy to inspect the data that's flowing through your React application. You can see the values of props and state, track changes over time, and even view the component's update history. Time-Traveling Debugger: You have the option to time-travel through the lifecycle of a component, making it easy to understand how a component's props and state change over time. You can step backward and forward through the component's updates, inspect the props and state at each point in time, and even revert to a previous state to reproduce and fix bugs. Performance Profiling: React DevTools includes a built-in profiler that helps you identify performance bottlenecks in your React application. You can profile the rendering performance of your components, view the time spent on each update, and analyze the component's update patterns to optimize performance and reduce unnecessary renders. Highlight Updates: You can efficiently highlight components that are updating frequently, making it easy to identify components that may be causing unnecessary renders. You can see which components are updating and how often, helping you identify performance issues and optimize your React application. Filter and Search: React DevTools provides powerful filtering and searching capabilities, allowing you to quickly find and inspect specific components or DOM elements in your React application. You can filter components by name, type, or update status, and even search for components based on their props or state values. Multiple React Versions: This React tool supports multiple versions of React, making it compatible with different projects that may be using different versions of React. You can switch between different React versions in the DevTools settings, allowing you to inspect and debug React applications with different dependencies. Accessibility Inspection: React DevTools includes an accessibility inspection feature that helps you identify and fix accessibility issues in your React components. You can check for accessibility violations, view accessibility properties, and get recommendations for improving the accessibility of your React application, ensuring that your UI is accessible to all users. Redux DevTools If you're using Redux, a popular state management library for React, Redux DevTools is a must-have tool. It provides a set of powerful features for inspecting and debugging Redux stores, including actions, state changes, and middleware. Redux DevTools also offers advanced features like a time-traveling debugger, which allows you to step through the history of your Redux store and replay actions, making it easier to understand and troubleshoot complex state management logic. Top Features of Redux DevTools Time Travel: React developers can easily time travel through the state changes in their Redux store. They can replay past actions, inspect the state at different points in time, and understand how the state changes throughout an application's execution. Action Monitoring: Redux DevTools logs all dispatched actions in the application, providing a comprehensive history of the actions that have been triggered. Developers can inspect the details of each action, including its type, payload, and metadata, to understand how the state changes in response to different actions. State Inspection: You’ll get a visual representation of the current state of the Redux store. Developers can inspect the state tree, view the values of individual properties, and track changes to the state in real-time as actions are dispatched. Time-Traveling Debugger: It includes a debugger that allows developers to set breakpoints, step through code, and inspect the state and actions at each step. This can be extremely helpful for understanding how the state changes in response to different actions and debugging complex Redux-based applications. Action Replay: The developers can quickly replay past actions, which can be useful for reproducing and fixing bugs. Developers can select a specific action from the action history and replay it to see how it affects the state and flow of the application. Middleware Support: It supports middleware, which are functions that intercept actions before they reach the reducers. This allows developers to inspect and modify actions before they are processed by the Redux store, providing powerful debugging capabilities. Keyboard Shortcuts: This impressive tool provides a range of keyboard shortcuts that make it easy to navigate and interact with the tool. This can help developers quickly access different features and perform common actions, improving their productivity. Integration with the Redux Ecosystem: Redux DevTools integrates seamlessly with the broader Redux ecosystem, including popular libraries like Redux Thunk, Redux-Saga, and Redux Observable. This allows developers to inspect and debug the behavior of these middleware and other Redux-related tools. Customization: You’ll get a wide range of customization options, allowing developers to configure the tool to suit their preferences and workflow. Developers can customize the appearance, behavior, and features of Redux DevTools to match their specific needs. Create a React App Create React App is a popular command-line tool that allows you to quickly bootstrap a new React project with a preconfigured development environment. It sets up a minimalistic yet powerful development setup with modern features such as hot module replacement, which allows you to see changes in your code without refreshing the page, and a built-in development server with automatic error reporting. Create React App also comes with a set of built-in scripts for common development tasks, such as building and deploying your React application, making it a great tool for getting started with React development. Top Features of the Create React App Zero Configuration: You can efficiently deal with a pre-configured setup for a React application out of the box, eliminating the need for developers to configure and set up various build tools and dependencies manually. This allows developers to quickly start building React applications without spending time on initial setup. Developer Experience (DX): Create React App prioritizes the developer experience by providing features such as hot module replacement, which allows for fast and seamless development with automatic reloading of changes without requiring a full page refresh. It also includes helpful error messages with detailed stack traces for easier debugging. Production-Ready Builds: You can generate optimized, and production-ready builds for React applications. It automatically optimizes assets, minifies code, and performs other performance optimizations to ensure that the production builds are efficient and optimized for performance. Customizable Configuration: While Create React App comes with a sensible default configuration, it also provides options for customization. React developers can "eject" from the default setup and access and modify the underlying configuration files to tailor the build process to their specific requirements. Built-in Support for Modern JavaScript Features: This development tool supports modern JavaScript features such as ES6, ES7, and beyond, allowing developers to write modern JavaScript code without having to configure and set up transpilers manually. CSS and Style Support: It supports various CSS and styling options out of the box, including CSS modules, Sass, Less, and CSS-in-JS solutions like styled-components and emotion, making it easy for developers to choose their preferred styling approach. Development Server: Create React App includes a built-in development server that allows developers to run and test their React applications locally with automatic reloading and other development-friendly features. Extensibility: Create React App provides an extensible and pluggable architecture, allowing developers to add additional tools, libraries, and configurations to their React application as needed without ejecting from the default setup. Easy Deployment: Create React App generates production-ready builds that can be easily deployed to various hosting platforms or integrated into a continuous integration and deployment (CI/CD) pipeline for seamless deployment to production. Storybook Storybook is a powerful tool for building, documenting, and testing React components in isolation. It provides a development environment where you can create interactive component showcases with different variations and states, making it easier to visualize and test the behavior of your components in different scenarios. Storybook also comes with a set of add-ons for tasks such as accessibility testing, documentation generation, and visual testing, which can help you build more robust and accessible React applications. Top Features of a Storybook Component Isolation: Storybook allows developers to develop and test UI components in isolation, separate from the main application. This isolation makes it easier to identify and fix issues related to individual components without affecting the rest of the application. Component Explorer: Storybook provides a component explorer that acts as a visual documentation tool for UI components. It allows developers to browse through a visual catalog of components, views their various states, and interact with them to understand their behavior and usage. Interactive Development Environment (IDE): Storybook provides an interactive development environment where developers can create and edit components in real time. This makes it easy to experiment with different component configurations, styles, and interactions to fine-tune their appearance and behavior. Component Testing: Storybook comes with built-in testing capabilities, allowing developers to write and run tests for individual components. This helps in identifying and fixing issues related to component functionality, behavior, and performance, ensuring the quality and reliability of the components. Customizable Themes and Styles: Storybook allows developers to customize the appearance and behavior of the component explorer to match their application's branding and styling. It provides support for custom themes, styles, and layouts, making it easy to integrate Storybook into existing design systems and workflows. Extensibility: Storybook is highly extensible and supports a wide range of plugins and add-ons. These plugins and addons can be used to enhance the functionality of Storybook, such as by adding support for different UI frameworks, integrating with third-party tools, and extending testing capabilities. Collaboration and Documentation: Storybook makes it easy for teams to collaborate on component development by providing features like built-in version control, documentation generation, and sharing of component stories. This makes it easier to maintain a consistent design system, document component usage, and share component examples across the team. Support for Multiple Frameworks: Storybook supports a variety of popular UI frameworks, such as React, Vue, Angular, and more. This makes it a versatile tool that can be used in different front-end development environments, allowing developers to work with their preferred UI framework. Hot Module Replacement (HMR): Storybook uses Hot Module Replacement (HMR) to provide a fast development experience by allowing developers to see changes in their components in real time without requiring a full page reload. This speeds up the development process and improves productivity. Addons Ecosystem: Storybook has a vibrant ecosystem of addons that provide additional functionality, such as design system integration, accessibility testing, internationalization, and more. These add-ons can be easily installed and configured, enhancing the capabilities of Storybook for specific use cases. ESLint ESLint is a popular JavaScript linter that can be configured to work seamlessly with React applications. It helps you enforce coding standards, catch common errors, and prevent potential bugs in your React codebase. ESLint comes with a set of predefined rules for React, and you can also configure custom rules based on your project's requirements. By integrating ESLint into your development workflow, you can catch coding mistakes early and ensure consistent code quality across your React application. Top features of ESLint Customizable Rules: ESLint allows developers to customize rules according to their project's specific coding standards. It comes with a default set of rules, but developers can configure it to suit their requirements. Extensibility: ESLint is highly extensible, allowing developers to create their own rules or use third-party plugins to enhance its functionality. This makes it flexible and adaptable to different coding styles and project requirements. Multiple Configuration Options: ESLint supports multiple configuration options, including configuration files (e.g., .eslintrc.js, .eslintrc.json, etc.), inline configuration comments, and shared configuration files. This allows developers to configure ESLint in a way that best fits their workflow. Support for ECMAScript Versions: ESLint supports different ECMAScript versions, including ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, and ES2021. This makes it suitable for projects using different JavaScript language versions. Code Fixing: ESLint not only identifies coding errors but also provides automatic code-fixing suggestions for many common issues. Developers can use the --fix option to automatically fix issues reported by ESLint, which helps maintain code quality and consistency. Editor Integrations: ESLint has integrations with popular code editors such as VSCode, Sublime Text, Atom, and many others. These integrations provide real-time feedback, making it easier for developers to identify and fix issues as they write code. Support for Plugins and Shareable Configurations: You can quickly support plugins that can extend their functionality and shareable configurations that allow developers to define a set of rules and share them across projects. This makes it easy to maintain consistent coding standards across multiple projects. Wide Range of Rules: You can efficiently define a large number of built-in rules that cover various coding conventions, best practices, and potential issues. These rules help in catching coding errors, enforcing coding standards, and improving code quality. Command-line Interface (CLI): This dev tool provides a command-line interface (CLI) that allows developers to run it from the command line, making it suitable for integration into build systems and continuous integration (CI) pipelines. Active Community and Regular Updates: ESLint has a large and active community of users and contributors, which ensures regular updates, bug fixes, and improvements. It is actively maintained and widely used in the JavaScript ecosystem. React Router React Router is a powerful routing library for React that allows you to create declarative, client-side routing in your applications. It provides a set of routing components that you can use to define routes, nested routes, and route transitions, making it easier to handle navigation and URL routing in your React applications. React Router also comes with advanced features such as lazy loading, code splitting, and server-side rendering, which can help you optimize the performance and user experience of your React applications. Top features of React Router Declarative Routing: React Router allows developers to define routes using declarative syntax, making it easy to specify how different components should be rendered based on the URL. Routes can be defined as components, making it straightforward to manage the state and props of the routed components. Nested Routing: React Router supports nested routing, allowing developers to create complex route hierarchies with parent and child routes. This enables the creation of multi-level navigation structures and allows for more granular control over how components are rendered based on the URL. Dynamic Routing: It allows for dynamic routing, where routes can be generated based on data or user input. This makes it possible to create dynamic and data-driven user interfaces that can adapt to changing data or user interactions. Route Parameters: With its active support for route parameters, developers can define dynamic segments in URLs that can be used to pass data to components. This makes it possible to create dynamic and personalized views based on URL parameters. Redirects and Navigation: This React development tool provides built-in support for handling redirects and navigation between different routes within the application. This allows for smooth navigation between different views and enables developers to create intuitive user experiences. History API Integration: React Router integrates with the HTML5 History API, allowing for smooth navigation without page reloads. This enables a seamless user experience and allows for more efficient handling of routing in SPAs. Middleware Support: It has integral support for middleware, allowing developers to add custom logic or behavior during the routing process. This makes it possible to implement features such as authentication, authorization, and data fetching as part of the routing logic. Route Guards: React Router supports route guards, which are functions that can be used to protect routes based on certain conditions. Route guards can be used to implement authentication and authorization logic to restrict access to certain routes or views based on user roles or permissions. Linking and Navigation Components: React Router provides a set of built-in components for linking and navigation, such as Link and NavLink, which make it easy to create clickable links and navigation menus in the application. These components automatically handle URL updates and provide a consistent way to navigate between different routes. Extensibility: React Router is highly extensible and allows developers to customize its behavior by creating their route components, hooks, and higher-order components. This provides flexibility in managing routing logic and enables developers to tailor the routing behavior to their specific needs. In Summary React Developer Tools are essential for any React developer looking to streamline their development process, debug efficiently, and optimize their React applications for better performance. With its powerful features and intuitive interface, React Developer Tools is a must-have toolset for modern web development with React. Happy coding!
Face recognition, as a biometric recognition technology based on artificial intelligence, has been widely applied in many fields in recent years. In web applications, facial recognition-based identity authentication is also becoming increasingly popular. At the same time, the user experience continues to improve. This article will introduce how to integrate FaceIO in Vue.js and Golang to achieve identity authentication for facial recognition. In web applications, the security and convenience factors brought by facial recognition-based identity authentication can enhance the user experience. FaceIO, as a third-party technology service provider, provides facial identity recognition services based on artificial intelligence. Moreover, FaceIO provides an online JavaScript library. It can be integrated very easily into web applications. The reference to the online JavaScript library can be found in the following code: JavaScript <script src="https://cdn.faceio.net/fio.js"></script> The project code corresponding to this article has been uploaded to GitHub. The uploaded code is a complete project code. The code is based on the MIT protocol and has no restrictions. Plain Text Technology Stack Front-end: Vue.Js v2.7.14, Node.Js v18.13.0, JQuery Back-end: Golang v1.13.15 Development Tool: GoLand 2019 If you are interested, you can download it from GitHub. The UI framework for the front end of this project uses Bootstrap and references the use of Gentella. Take a screenshot of the login below Before integrating web applications, you need to first apply for a Public ID for your application from FaceIO's official website. The application process is very simple and offers a free trial count. This article will not introduce the application process. If you are interested, you can log in to FaceIO's official website to take a look. How To Integrate FaceIO in the Front End of Vue.js Step 1: Create Vue Engineering Files in the Development Tool GoLand This project uses Node.Js version 18.13.0. After installing the plugin Vue.js in GoLand, we can create Vue project files. To install plugins, you need to enter Vue.js in "File>Settings>Plugins" to find and install them. Step 2: Reference “fio.js” in Index. HTML in the Directory “Public” The code is shown below: HTML <body> <div id="app"></div> <!-- built files will be auto injected --> </body> <script src="https://cdn.faceio.net/fio.js"></script> <script src="ajaxScript.js"></script> <script src="jquery.min.js"></script> <script type="text/javascript"> let myFaceIO function initFaceIO(result) { myFaceIO = new faceIO(result.Result.FaceIOAppCode); window.myFaceIO=myFaceIO } jPost("config","",initFaceIO) </script> In the above code, use JQuery to obtain the Public ID registered by the web application in FaceIO from the back end. The purpose of this design is to prevent the Public ID from being directly exposed in the code. If the Public ID is written dead in the code, the flexibility is very low. In my customized JavaScript file "ajaxScript.js," I implemented a common function for communicating with the back-end through JQuery. The custom function jPost() is responsible for interacting with the back end. At the same time, after data interaction, call the callback function initFaceIO() in the result. All data interactions use JSON data format. In the callback function initFaceIO(), initialize the FaceIO object. Code for initializing FaceIO: JavaScript myFaceIO = new faceIO(result.Result.FaceIOAppCode); After initialization, use: JavaScript window.myFaceIO=myFaceIO This will make myFaceIO a global variable. It can take effect in Vue's framework code and can be used. Please note that the JavaScript files "ajaxScript.js" and "jquery.min.js" referenced here are both in the directory "public" and do not use Vue's library files. Step 3: How To Use FaceIO in the Vue Framework In this project, I used JQuery in the Vue framework and customized the JavaScript library file "scripts.js." In this JavaScript library file, JQuery has been re-encapsulated, and some operation functions have been customized. So, in order to be globally effective, a reference was made in the file "main.js." At the same time, the code also uses the UI framework Bootstrap, which needs to be referenced in the file "main.js." The code for the file "main.js" is shown below: Vue.js Component import Vue from 'vue' import App from './App.vue' import router from './router/index.js' import {AjaxPost} from './assets/build/js/scripts.js' import './assets/vendors/bootstrap/dist/css/bootstrap.min.css' import './assets/vendors/font-awesome/css/font-awesome.min.css' import 'bootstrap/dist/js/bootstrap.min.js' Vue.prototype.AjaxPost = AjaxPost Vue.config.productionTip = false new Vue({ el: '#app', router, render: h => h(App) }) Set the HTTP access address of the back-end service in the custom JavaScript library file "scripts.js" JavaScript const serverUrl='http://127.0.0.1:8888/' Because in this project, the front-end and back-end are separate development frameworks. I implemented the back end using Golang. After this back-end is started, it is an independent HTTP server that will occupy the port number. 8888 is my custom listening port number. The interface address of this back-end service can be customized and deployed according to your own project situation, and you can modify the value of the custom constant "serverUrl." After completing the above code, you can use the FaceIO function in the Vue file. For example, in "userLogin. vue", the usage of the facial authentication code is shown below: Vue.js Component window.myFaceIO.authenticate({ "locale": "auto" }).then(userInfo => { checkFaceLogin(userInfo); }).catch(errCode => { console.log(errCode); window.myFaceIO.restartSession(); }) Among them, the function checkFaceLogin() is a custom callback function. It is used to process the user authentication result data returned by FaceIO after authentication. We can proceed with the next step of business logic processing based on the verification result data returned by FaceIO. Take a screenshot of the runtime as shown below: For example, in 'sysDashboard.vue', the usage of facial identity registration codes can be found below: Vue.js Component <script> export default { name: 'sysDashboard', activated() { this.AjaxPost("dashboard","",initData); function initData(result) { if(result.Code===1){ let fid=result.Result.FacialId if(fid.length>4){ document.getElementById("myFacialId").innerHTML=fid document.getElementById("FacialIdReg").disabled=true; } } } }, methods: { faceReg: function() { const that=this window.myFaceIO.enroll({ "locale": "auto" }).then(userInfo => { addFacialAuth(userInfo); }).catch(errCode => { console.log(errCode); }) function addFacialAuth(userInfo) { let obj = {}; obj.FacialId = userInfo.facialId; that.AjaxPost("userEdit",obj,addFacialAuthEnd); } function addFacialAuthEnd(result) { if(result.Code===1){ document.getElementById("myFacialId").innerHTML=result.Result.FacialId document.getElementById("FacialIdReg").disabled=true; }else { alert("add Facial Auth fail") } } } } } </script> Among them, the function faceReg() is a custom facial recognition registration function. The subfunction addFacialAuth() is a custom callback function. It is used to process the user registration result data returned by FaceIO after identity registration is completed. We can use the registration result data returned by FaceIO. Proceed to the next step of business logic processing. Please note the calling method of the custom function AjaxPost() here. It is not directly called using the method of 'this. AjaxPost()'. But in the first line of the function faceReg() JavaScript const that=this Define a constant 'that' before using 'that. AjaxPost()' in the subfunction addFacialAuth(). This is because in the subfunction addFacialAuth(), 'this. AjaxPost()' is invalid. Take a screenshot of the runtime as shown below: How To Integrate FaceIO in Golang’s Back-End In the code of this project, Golang is used to implement back-end services. Golang has good concurrency performance, memory management automation, and other advantages, so it has been widely used in building back-end services. Please install Golang first. In this project code, I am using version 1.13.15. After installing Golang, it is easy to create Golang project files in the Goland development tool. Also, please note one point. In this project, there is no persistent data storage in Golang. The data is all in memory, and if the service is restarted, the data will be initialized. Because this project is a demo, it is just a simple example. Step 1: Create an HTTP Server In the code of this project, an HTTP server is created in the file "api_router. go". Here is the code: Go package routes import ( "../conf" "../handlers" "fmt" "log" "net/http" ) func ApiRouter() { http.HandleFunc("/dashboard", handlers.Dashboard) http.HandleFunc("/config", handlers.Config) http.HandleFunc("/login", handlers.Login) http.HandleFunc("/loginWithFace", handlers.LoginWithFace) http.HandleFunc("/userEdit", handlers.UserEdit) port := conf.GetEnv().ServerPort if port == "" { port = "8080" log.Printf("Defaulting to port %s", port) } log.Printf("Listening on port %s", port) log.Printf("Open http://localhost:%s in the browser", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) } In the above code, use the ‘http.HandleFunc()’ function to register multiple router functions into the HTTP multiplexer. These router functions will handle all requests from the client. Finally, call the ‘http.ListenAndServe()’ function to start listening to HTTP requests on the port. When the current end sends a request to the back end, the corresponding router function will be executed, and a response will be returned. Step 2: Receive the Request From the Vue.js Front-End To facilitate code management, I have included all these router functions in the package "handlers." For example, obtaining the public ID of an application registered in FaceIO. The code for this function is in the file "config.go", as shown below: Go package handlers import ( "../conf" "../model" "encoding/json" "io" "net/http" ) func Config(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") result := make(map[string]string) result["FaceIOAppCode"] = conf.GetEnv().FaceIOAppCode jsonResult := new(model.JsonResult) jsonResult.Code = conf.GetEnv().CodeSuccess jsonResult.Msg = conf.GetEnv().MsgSuccess jsonResult.Result = result msg, _ := json.Marshal(jsonResult) _, _ = io.WriteString(w, string(msg)) } Please note that in the above code, there are two lines of code. Go w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") This is the setting that allows cross-domain access. Because in my local development environment, the front-end startup is an independent HTTP server, and the back-end startup is also an independent HTTP server. The data interaction between the front-end and back-end belongs to cross-domain access. If you deploy in a production environment, you may be able to run using an HTTP server. For example, using Nginx, you can set up a reverse proxy. This belongs to the same domain access, so these two lines of code are not required. Because in the code of this project, both the front-end and back-end data interactions use JSON data format. So, in the above code, call the function ‘json.Marshal()’ to format the data. Viewing Application Usage on the FaceIO Console As a facial recognition technology service provider, FaceIO provides many services on its console. For example, facial detection, facial search, facial authentication, live body detection, and so on these functions can be completed by simply calling its provided online JavaScript library. In addition, after you register and log in to its management back-end, you can create different applications and assign multiple public IDs based on your business scenario. There are records of the usage of each application, as shown in the screenshots below: In this article, a brief explanation is provided to introduce how to integrate FaceIO using Vue.js and Golang to implement a facial recognition-based identity authentication system. In the Vue.js environment, FaceIO provides an online JavaScript library for facial detection and recognition. It can directly start the user's computer's camera, send the captured facial photos directly to the FaceIO server for processing, and return the recognition results to the front end. The facial photos taken do not need to go through our application. Through this demonstration project, we can not only learn how to build complete web applications using two different programming languages but also learn how to integrate facial recognition technology to enhance application security and user experience. These skills and knowledge can help us better understand computer science and software development and enhance our professional competitiveness. Through practice, we can better understand the working principle of the code and master some practical skills and best practices. In this process, we can make new friends and colleagues, explore and create more valuable things together. I hope everyone can maintain an attitude of continuous learning and progress, constantly exploring and creating more value.
Conditional statements are used for making decisions based on different conditions. Also, conditionals in JavaScript allow you to execute different blocks of code based on whether a certain condition is true or false. Conditions can be implemented using the following ways: If If Else Else If Switch Ternary operators If Statement This statement shows truthy values used to check if the given conditions are true and then execute the block of code. JavaScript if(condition){ -----block of code which is going to execute----- } Let's understand this with an example. JavaScript let num = 10 if (num > 0) { console.log(num + "is a positive number") } //Output => 10 is a positive number// //In the above example, we set the condition that if the user enters any number greater than 0, then "if" condition got executed and it returns the output.// Else Statement It's the opposite of an If statement. So we will say that, if the If condition is not executed, which will happen when the given condition is false, then the Else statement gets executed. JavaScript if(condition){ -----block of code which is going to execute----- } else { -----block of code which is going to execute----- } Let's take an example and try to understand this. JavaScript //lets say we made a google form for football trails and age limit for the peoples who can apply for this is 16+. Now, If the user enter age more or less than the given age, certain blocks of code gets executed and give response accordingly.// let myAge = 15 if (myAge > 16) { console.log(myAge + " is valid age, you are eligible for trials.") } else { console.log(myAge + " is not a valid age, you are not eligible for the trials .") } //I Hope this clears how "If" and "Else" statements works// Else If This is used for most cases because there are multiple options to select from sometimes. Let's understand this with an example. JavaScript if(condition){ -----block of code which is going to execute----- } else if(condition){ -----block of code which is going to execute----- } else { -----block of code which is going to execute----- } Let's say we found an interesting website, and in order to get the most out of this website, it's asking us to make an account on it. As we are going through the process of making an account, it asks us to set questions and answers in case we lose our password so we can still be able to log in by giving the correct answer to the questions. Now, a few months pass and we want to sign in to that website but we couldn't remember our password, so the website gives us the option to sign in by giving answers to the questions we set previously. It gives us a question and four options to choose from. Que: What is your favorite color? a) blue b) Indigo c) pink d) red JavaScript let favColor = 'blue' if(favColor === 'indigo'){ console.log("indigo is not your favorite color.Try again") } else if(favColor === 'pink'){ console.log("pink is not your favorite color.Try again") } else if(favColor === 'red'){ console.log("Seriously, red, broooo Try again") } else if(favColor === 'blue'){ console.log("Thats my bro, blue is your fav color. Never forget your password again.") } else { console.log("Enter your fav color") } Switch Statement The switch statement is an alternative for If Else statements. The switch statement makes the code more concise and easier to read when you need to test a single variable against multiple possible values. JavaScript switch (case value) { case 1: console.log(' ') break; //Suppose, the condition is satisfied after end of case 1, then, 'break' terminates the code// case 2: console.log(' ') break; default: //default runs only if all the cases dont satisfy conditions.// console.log(' ') } Let's understand this with an example. JavaScript let theDay = 'tuesday' switch(theDay) { case'monday': console.log('Today is not monday'); break; case'tuesday': console.log('Yes, today is tuesday'); break; default: console.log('Please enter a valid day'); } //In this example, the code terminates after 2nd case, as the condition is satisfied at case 2// Ternary Operator It is a simple way of writing an If Else statement. It takes three values or operands: A condition Expression to execute if the condition is true Expression to execute if the condition is false Let's understand this with an example. JavaScript let playFootball = true playFootball ? console.log('you needs football boots to play on ground') : console.log('i dont wanna play') The ternary operator is useful when you need to make a simple decision based on a single condition. It can make your code more concise and easier to read, especially when used with short expressions. Conclusion This blog discusses various conditional statements in JavaScript, which allow the execution of different blocks of code based on certain conditions. This includes If, Else, Else If, Switch, and Ternary operators. If statements are used to check whether a condition is true, while Else statements execute when the If condition is false. Else If statements are used when multiple options need to be considered. Switch statements are an alternative to If Else statements and make the code more concise. Ternary operators are a simple way of writing If Else statements.
As a developer, you build software that addresses real-world challenges and positively impacts people's lives. Moreover, your work could attract significant interest from your peers and colleagues. And having a portfolio to showcase your skills and expertise would be exciting. Knowing this, I built a simple portfolio website allowing visitors to get to know me and my work! And you can also use it to create a portfolio for yourself. Building My Portfolio Site The Outcome First, let us take a look at the site that we are going to build. Currently, it is deployed in this URL. The site has five main pages: Landing, Projects, Contact, About, and a Detailed Project page. These are shown below. Selecting the Tech Stack and Architecture When I started building my site, I wanted to utilize a tech stack and an architecture that's faster to develop, and anyone could customize to create their portfolio. Therefore, I used React for my front end, with Bit adopting a component-driven architecture. Using Bit, I've split my portfolio application into five main scopes. Here, a scope is like a collaboration server for a collection of components. Design: The design scope holds the design system of the portfolio. It contains components such as the theming and generic elements combined to build feature components. Personal Portfolio: This scope contains the portfolio web application. It holds the application component that is deployed onto Netlify. Dev: The dev scope is created to maintain the development environment and configurations. For example, I've used a custom React 18 environment to ensure all components utilized React 18. Contact Me: The contact me scope was created to separate the contact me logic from the rest of the scopes. It's connected with an API to support form submission. Starters: The starters scope holds the component responsible for building a boilerplate for others to reuse this portfolio. In summary, the scopes are connected as shown below. The high-level scope usage architecture Building the Website: Step-by-Step Guide While developing this application, I followed several best practices in creating Bit components. Let's go through them one by one. Naming scopes: Initially, I used lengthy names such as portfolio-site-design-system as scope names. However, this limits me from reusing the design scope and components with other applications. Hence, I recommend using short, generic, and readable names like design. Naming namespaces: Inside a scope, you'd include several namespaces. For example, I've included several namespaces in the personal-portfolio scope, such as pages, apps and hooks. This makes your code organized and easy to maintain. And, it's often a Bit best practice to use plural nouns as a namespace name. For example, don't use page. Instead, use pages. Naming components: It's essential to understand that you are building independent components which can be built in an isolated environment. Hence, your components should only be aware of each by composition. For example, if you were creating a Button component, ensure that you name the component as Button and not LandingPageButton. Selecting Props: Try to make your component properties as relevant to your component as possible. For example, don't use landingPageVariant. Instead, use variant to toggle the Button variant. You should be able to customize a component without modifying its internal code (open-closed principle). Thus, use prop spreading to bring about complete customizability. One such implementation is shown here. Don't delete components: Do not delete a Bit component after you've exported it. This is because other components may depend on it. Therefore, it is an excellent practice to deprecate your component using bit deprecate. Start with failing test cases: With Bit, you can seamlessly integrate TDD into your development workflow. Every component has a test file that ensures you ship out thoroughly tested code. Therefore, always start developing your component by designing your test cases first. Start writing failing test cases (that reflect your actual output) and implement your component to ensure that your test cases pass. An excellent example of integrating TDD into your development workflow is shown here through the Projects entity component I've implemented. Initializing the Workspace Firstly, you will need a Bit workspace to develop your components. To do so, run the command shown below. bit new react my-workspace --default-scope showoff.design The snippet above will create a new React workspace titled my-workspace and will use a default scope named showoff. Note, you can change this to your preference. This means that out of the box, my components get exported to a scope named "design" in the "showoff" organization in Bit cloud. Run bit start to launch the development server, and run bit create react my-component-name to create a new React component. Next, I can start building my design and application scopes. Building the Design Scope The entire application was built using MUI. Therefore, I've extended the MUI components and provided custom implementations where necessary. For example, I extended the MUI theme to create my own theme component as shown here. import { alpha, Theme, ThemeOptions } from '@mui/material'; import { themeCreator } from '@showoff/design.theme.theme-creator'; import { baseTheme } from '@showoff/design.theme.base-theme'; /** * Function that returns a configured light theme * @param additionalConfigurations - Additional configurations to be applied to the theme * @returns Configured light theme */ export function lightTheme(additionalConfigurations?: ThemeOptions): Theme { return themeCreator( baseTheme(), { palette: { mode: 'light', primary: { main: '#6C5CE7', }, background: { default: '#FFFFFF', paper: '#FFFFFF', }, text: { primary: '#2B2B2B', }, divider: '#BABEC9', }, components: { MuiButton: { defaultProps: { disableRipple: true, disableElevation: true, variant: 'contained', }, styleOverrides: { root: { textTransform: 'none', color: '#FFFFFF', }, sizeSmall: { padding: '6px 16px', }, sizeMedium: { padding: '8px 20px', }, sizeLarge: { padding: '11px 24px', }, textSizeSmall: { padding: '7px 12px', }, textSizeMedium: { padding: '9px 16px', }, textSizeLarge: { padding: '12px 16px', }, }, }, MuiChip: { styleOverrides: { root: { color: '#6C5CE7', fontWeight: 700, height: '24px', borderRadius: '6px', padding: '4px 8px', verticalAlign: 'middle', alignItems: 'center', }, }, }, MuiInputBase: { styleOverrides: { root: { borderRadius: '8px', borderColor: '#EDEDED', }, input: { '&::placeholder': { color: '#707279', }, }, }, }, MuiInputLabel: { styleOverrides: { root: { color: '#707279', }, }, }, MuiToggleButton: { styleOverrides: { root: { borderRadius: '8px', borderColor: '#EDEDED', transition: 'all 0.3s ease', textTransform: 'none', height: '40px', '&.Mui-selected': { backgroundColor: '#6C5CE7', color: '#EDEDED !important', '&:hover': { backgroundColor: alpha('#6C5CE7', 0.8), }, '& .MuiTypography-root': { color: '#EDEDED', }, }, }, }, }, }, typography: { button: { fontWeight: 600, }, allVariants: { color: '#2B2B2B', }, fontFamily: 'CircularXX', }, }, { ...additionalConfigurations } ); } Here, I've created a custom light theme component that utilizes a custom palette. Likewise, I extended the MUI design system to build my design elements, as shown below. Design Scope The design scope contains the design system used for the portfolio site. Next, I implemented the site's navigation menus, layouts, inputs, and even a font family. I have designed these components as separate units, which enables them to be reusable and developed independently. For example, here is the application's main navigation menu that I implemented. The application header The figure above shows a header component that compiles the navigation menu for the application. The reusable and customizable header component allows you to change the links, theme toggle, and brand logo. Building the Developer Scope While developing the site, I noticed all my components had two main compositions: Light and Dark. These compositions utilized the Light and Dark theme in the design scope. By customizing the Bit developer environment tabs (Docs and Composition), I could use a single composition with a toggle button for theme selection. For example, let's look at the react-with-mui environment I developed. Exploring the custom environment The environment component has four main files: docs.tsx - The file that allows you to provide a custom implementation for the Documentation view of Bit mounter.tsx - The file that allows you to provide a custom implementation for the Composition view of Bit env.jsonc - The file that lets you declare the dev, runtime, and peer dependencies that will be used by the components that use the environment my-react-env.bit-env.ts - The file that lets you mount the custom docs, composition implementations to a component Here, I decided to update the docs.tsx and mounter.tsx file to utilize a ThemeToggle component to toggle between the Dark and Light themes for each component as shown below. import React, { useState } from 'react'; import { createMounter } from '@teambit/react.mounter'; import { Box } from '@mui/material'; import { ThemeProvider } from '@showoff/design.theme.theme-provider'; import { ThemeToggle } from '@showoff/design.theme.theme-toggle'; import { darkPortfolioTheme } from '@showoff/personal-portfolio.theme.dark-portfolio-theme'; import { lightPortfolioTheme } from '@showoff/personal-portfolio.theme.light-portfolio-theme'; /** * use the mounter to inject and wrap your component previews * with common needs like [routing](), [theming]() and [data fetching](). */ // eslint-disable-next-line react/prop-types export function MyReactProvider({ children }) { const [themeMode, setThemeMode] = useState<'light' | 'dark'>('light'); return ( <ThemeProvider theme={themeMode === 'dark' ? darkPortfolioTheme() : lightPortfolioTheme()} > <Box sx={{ p: 5 }> <Box sx={{ display: 'flex', justifyContent: 'flex-end' }> <ThemeToggle isDark={themeMode === 'dark'} onClick={() => setThemeMode(themeMode === 'dark' ? 'light' : 'dark')} /> </Box> {children} </Box> </ThemeProvider> ); } /** * to replace that mounter component for different purposes, just return a function * that uses ReactDOM to render a node to a div. */ // @ts-ignore export default createMounter(MyReactProvider) as any; The snippet shown above creates a custom implementation for the Composition view. It uses a Theme Toggle to allow Dark/Light toggling and applies the theme for the children that has been passed. After designing my environment, I had to update my existing components with the new environment. To do so, I executed the command displayed below. bit envs set <<COMPONENT-NAME>> <<ENV-NAME>> Hereafter, I was able to directly utilize theme toggling within the component through the env as shown below. This reduced the composition code in half, just having one composition allowing to toggle between the themes. Building the Application After building the design scope using the custom environments, I created an application component that is deployable to host the entire site. The application component includes all the pages, and the Bit Netlify Deployer is used to deploy the application directly onto Netlify. The application is hosted on the URL linked at the beginning of this post. const { Netlify } = require("@teambit/cloud-providers.deployers.netlify"); const { splitChunks, addSitemap, } = require("@showoff/dev.transformers.portfolio-transformer"); const { prerenderRoutes } = require("./prerender-routes"); const netlifyConfig = { accessToken: process.env.NETLIFY_AUTH_TOKEN, stagingSiteName: "portfolio-showoff-staging", productionSiteName: "portfolio-showoff-production", team: "enlear", redirects: "_redirects", webpackTransformers: [ splitChunks, (config, context) => addSitemap(config, context, [...prerenderRoutes]), ], }; const PortfolioApp = { name: "portfolio", entry: [require.resolve("./portfolio.app-root")], prerender: { routes: ["/"], }, webpackTransformers: [ splitChunks, (config, context) => addSitemap(config, context, [...prerenderRoutes]), ], deploy: Netlify.deploy(netlifyConfig), }; module.exports.default = PortfolioApp; App Component This deployer runs whenever you tag your application component, thus simplifying the entire deployment process. Wrapping Up In this blog post, I've highlighted the most important aspects to consider when building your own. I've also created a Bit Starter, which helps you generate a boilerplate code of the portfolio website, which you can customize to build your portfolio in under 10 minutes. Open up a terminal and execute the command shown below. bit new portfolio portfolio-workspace --aspect showoff.starters/portfolio By doing so, Bit will create a new workspace with the components required to deploy a copy of the portfolio as your own! To explore the complete codebase, look at my GitHub repository or visit my Bit cloud account to inspect the five scopes. I hope that you have found this article helpful. Thank you for reading!
Cypress is a popular end-to-end testing framework used for web applications. It is an open-source JavaScript testing tool designed to make testing web applications fast, easy, and reliable. Cypress allows developers to write automated tests that simulate user interactions and verify the behavior of the applications. We can use Cypress with JavaScript or TypeScript for development, but JavaScript is the primary language used with Cypress. Cypress is built on top of JavaScript and uses many of the same concepts of modern web development, such as using a browser-based environment to run tests and interacting with web elements using CSS Selectors. When writing tests in Cypress, you can use JavaScript to create test cases that simulate user interactions and validate the behavior of your application. Cypress also provides a powerful set of built-in commands and assertions to help you write tests quickly and easily. However, before explaining Cypress JavaScript in detail, we’ll see why we use JavaScript for Cypress test case automation. Why Cypress With JavaScript? There are several reasons why Cypress and JavaScript work well together: Cypress Is Written in JavaScript Cypress is written in JavaScript, so developers who are already familiar with JavaScript will find it easier to work with Cypress. Cypress Has Extensive Support for JS Frameworks Cypress has good support for popular JavaScript frameworks like React, Vue, and Angular. This means that developers building applications using these frameworks can easily write tests for their applications using Cypress. Cypress Has a Rich API for Interacting With Web Applications Cypress provides a rich API for interacting with web applications, which includes methods for interacting with the DOM, making HTTP requests, and handling events. This API is designed to be easy to use and well-documented, making it easy to write tests using JavaScript. Debugging Support Cypress provides a comprehensive debugging experience, including built-in debugging tools and the ability to step through tests. Community Support There is a vast community of JavaScript developers, which means that plenty of resources are available online to help developers with any issues they encounter while using Cypress. Cypress uses a unique architecture that enables it to run tests in the same context as the application being tested. This means that Cypress has access to the application's DOM, network traffic, and even its backend server. This architecture allows for faster and more reliable tests and a more intuitive and powerful testing experience. Trends of Cypress on GitHub The data below is gathered from the official site of Cypress GitHub repository: Stars: 43.1k Forks: 2.8k Used By: 769k Releases: 302 Contributors: 435 Benefits of Using Cypress JavaScript for Automation Cypress JavaScript provides several features that make it easy to write and run tests, debug issues, and ensure the quality of web applications. We'll explore some of the key features of the Cypress automation tool in detail. Easy Setup Cypress JavaScript is very easy to set up and use. It can be easily installed with npm and requires minimal setup. Cypress also comes with a user-friendly graphical interface, which makes it easy for developers to navigate and use. Comprehensive APIs Cypress JavaScript provides a rich set of APIs for interacting with the DOM, making HTTP requests, and more. This makes it easy to write tests that simulate user interactions with your web application. Real-Time Reloads Cypress JavaScript provides real-time reloads, which means that any changes made to the code or tests are instantly reflected in the browser. This saves developers time and makes it easy to see the impact of changes in real-time. Automatic Waiting Cypress JavaScript automatically waits for assertions to pass and for elements to appear before executing the next command. This makes tests more stable and reduces the likelihood of false negatives. Debugging Cypress JavaScript comes with built-in debugging tools, making it easy to troubleshoot and fix failing tests. Developers can use these tools to inspect the DOM, debug JavaScript code, and more. Time Travel Cypress JavaScript provides a unique feature called "time travel" that allows you to go back and forth in time, inspecting and debugging your application at any point in the test. This feature can save developers a lot of time and effort when trying to debug complex test scenarios. Cross-Browser Testing Cypress JavaScript supports cross-browser testing and can run tests on different browsers. This makes it easy to ensure that your application works as expected across different browsers. Automatic Screenshots and Video Cypress JavaScript can automatically take screenshots and record videos of your tests, making it easy to see what happened during a test run. This can be helpful when trying to identify issues or bugs in your application. Custom Commands Cypress JavaScript allows developers to create custom commands that can be reused across different tests. This makes it easy to create a library of common test commands and reduces the amount of code duplication. Continuous Integration Cypress JavaScript can be easily integrated with popular continuous integration (CI) tools like Jenkins, CircleCI, and Travis CI. This makes it easy to run tests automatically as part of your CI/CD pipeline. How To Install and Set up Cypress Cypress end-to-end testing framework is easy to set up and use. Here are the steps to install and set up Cypress. 1. Install Node.js Cypress requires Node.js to be installed on your system. You can download and install the latest version of Node.js from the official website https://nodejs.org. 2. Create a New Project Create a new directory for your Cypress project and navigate into it using the command line. Run npm init to create a package.json file. 3. Install Cypress Install Cypress by running the following command in the command line. npm install cypress --save-devHere --save-dev is a command-line option used with Node Package Manager (npm) to install and save development dependencies for a project.ORyarn add cypress --devHere, we can see version 12.6.0 installed. Save it as a development dependency in your package.json file. Open Cypress Once installed, you can open Cypress by running the following command in the command line. yarn run cypress open The default folder structure of Cypress is shown below. You can create test cases under the folder “e2e”. Project Structure of Cypress From the screenshots, you can see that Cypress has created a default folder hierarchy when it opens for the first time. Below are the details for each of these folders/files created by Cypress. Cypress: This is the main folder for your Cypress project. It contains all the subfolders and files related to your tests. e2e: This is the main folder to store all your tests. We can add the Basic, End to End Test, API, Visual or Cucumber test here. All your spec files will be here. fixtures: The fixtures/folder is where you can store static data files, such as JSON or CSV files, which your tests can use. For example, you may have a fixture file containing a list of test data that multiple test cases can use. support: This folder contains reusable test code that can be shared across multiple test specs. This can include custom Cypress commands, page objects, or utility functions. There are two files inside the support folder: commands.js and index.js. command.js: This is the file where you add your commonly used functions and custom commands. It includes the common functions that you may call to use in different tests. Cypress created some functions for you, and you can override them here if you want. e2e.js: This file runs before every single spec file. In this file, we keep all global configurations and can modify them as per the requirement. By default, it imports only commands.js, but you can import or require other files to keep things organized. node_modules: All the node packages will be installed in the node_modules directory and available in all the test files. So, in a nutshell, this is the folder where NPM installs all the project dependencies. cypress.config.js: This is a configuration file used by the Cyprestesting framework to customize the behavior of the framework and tests. This file can be used to configure various settings for your tests, such as the base URL for your application, viewport size, test timeout values, and other options. The cypress.config.js file is located in the root of the Cypress project, alongside the cypress folder. When Cypress runs, it will automatically load this file and use the configuration values specified in it. Apart from the above folders, we have some more folders like Screenshots, Downloads, and Videos to store different related files. Basic Constructs of Cypress Cypress uses the same syntax as Mocha for writing test cases. The following are some of the key constructs frequently used in Cypress test development. describe(): This method is used to group related test cases. It takes two arguments: a string that describes the group of test cases and a callback function that contains the individual test cases. it(): This method is used to define a test case. It takes two arguments: a string that describes the test case and a callback function that contains the actual test code before(): This method is used to run a setup function before any test case in a particularly described block. It can be used to set up the test environment, initialize variables, and perform other setup tasks. after(): This method is used to run a cleanup function after all the test cases in a particularly described block have finished running. It can be used to clean up the test environment, close open connections, and perform other cleanup tasks. beforeEach():This method is used to run a setup function before each test case in a particularly described block. It can be used to reset the state of the test environment and perform other setup tasks. afterEach(): This method is used to run a cleanup function after each test case in a particularly described block has finished running. It can be used to reset the state of the test environment and perform other cleanup tasks. .skip(): When dealing with a large codebase and wanting to concentrate on specific tests or subsets of tests, the .skip() function provides a handy means to temporarily prevent certain tests from being executed. Types of Testing Performed Using Cypress JavaScript Cypress is a JavaScript-based testing framework that provides a comprehensive set of features for testing different aspects of web applications, including UI, API, mobile responsiveness, and accessibility. Here's how Cypress JavaScript can be used for each of these types of testing: User Interface (UI) Testing Cypress allows easy and comprehensive UI testing. Cypress provides APIs that allow developers and testers to interact with the web application's UI elements and simulate user interactions while performing Cypress UI automation. The Cypress APIs are capable of validating the application's functionality by allowing users to click on buttons, enter data into forms, and navigate between different pages. It also provides an automatic waiting mechanism that waits for the UI to finish rendering, ensuring that the tests are accurate, reliable, and less flaky. Cypress JavaScript offers significant benefits in dealing with flakiness due to its ability to automatically retry failed tests and provide reloading in real-time. Application Programming Interface (API) Testing Cypress can also be used to test the web application's APIs. Testers can use Cypress to send HTTP requests and validate the responses. It provides a built-in command called cy.request() that enables testers to send HTTP requests and make assertions based on the received responses while performing Cypress API testing. Additionally, Cypress JavaScript supports response interception, mocking, and stubbing, allowing testers to simulate different server responses. Accessibility Testing Cypress can be used to test the accessibility of the web application. Cypress JavaScript provides an integrated accessibility testing library called cypress-axe, which uses the Axe engine to detect accessibility issues and generate accessibility violation reports. With Cypress accessibility testing, it's possible to test the web application's accessibility features and ensure that it's usable by everyone, regardless of any disabilities. Component Testing Cypress is also suitable for component testing, which is useful when working with a modular application that utilizes several components. Cypress JavaScript offers an isolated test environment, making it easy to test individual components and ensure that they work as expected. This testing approach helps to identify and resolve issues early in the development process, reducing the chances of the web application crashing. Mobile Responsiveness Testing Cypress can be used for web application responsive testing across different viewports. Cypress provides a viewport resizing feature, allowing testers to simulate mobile devices' different screen sizes, making it easy to test the web application's responsiveness. Additionally, Cypress JavaScript can integrate with third-party mobile testing platforms like LambdaTest, which offers access to a vast range of mobile devices and operating systems. UI Testing With Cypress JavaScript Cypress provides easy access to the application's DOM, making it easy to manipulate and assert the state of individual elements on the page. It also supports plugins, which can be used to extend its functionality and integrate with other tools and services. These are the reasons Cypress JavaScript is preferred for UI testing. UI Test Case: Example 1 Let’s create a new folder under the e2e folder named “LambdaTest” to perform Cypress UI testing. Create the first spec with the name LoginWithValid_Invalid_Data.cy.js under the folder LambdaTest. In the below test case, we are covering login into LambdaTest with Valid/Invalid data. For demo purposes, we are using the LambdaTest eCommerce Playground site. Use Case Login into the application and update the newsletter subscription JavaScript describe('Login and Subscribe', () => { it('Logs in and subscribes to newsletter', () => { // Visit the site cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') // Click the login button cy.get('[value="Login"]').click() // Enter valid email and password cy.get('#input-email').type('lambdatestnew@yopmail.com') cy.get('#input-password').type('Lambda123') // Click the login button cy.get('[value="Login"]').click() // Verify successful login cy.url().should('include', 'index.php?route=account/account') cy.contains('My Account').should('be.visible') // Subscribe to newsletter cy.contains('Newsletter').click() cy.get('#input-newsletter-yes').click({force:true}) cy.get('[value="Continue"]').click() // Wait for subscription success message cy.get('.alert-success').should('contain', 'Success: Your newsletter subscription has been successfully updated!') }) }) Output Below is the output of the above-executed test case. UI Test Case: Example 2 Create the second spec with the name SerachWithValid_InvalidData.cy.js under the folder LambdaTest. In the below test case, we are covering the search with valid and invalid data and verifying the search result. JavaScript describe('Search with Valid/ Invalid data' , () => { beforeEach(() => { cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') }) it('Searches for the text "Apple" and displays results', () => { // Enter search data and submit form cy.get('[name="search"]').eq(0).type('Apple') cy.get('.type-text').click() // Verify search results cy.url().should('include', 'search=Apple') cy.contains('Search - Apple').should('be.visible') cy.get('.product-layout').should('have.length.gt', 0) }) it('Displays message with no search results for invalid search term', () => { // Enter search term that returns no results and submit form cy.get('[name="search"]').eq(0).type('abc') cy.get('.type-text').click() // Verify message for no search results cy.contains('There is no product that matches the search criteria.').should('be.visible') }) }) Output Below is the output of the above-executed test case. API Testing with Cypress JavaScript Developers and testers using Cypress can create tests that send HTTP requests to their application's API and validate the corresponding responses. We can use Cypress API to write automated tests that simulate user interactions with the application's API endpoints. This type of testing is especially useful for testing API responses, validating data input and output, and verifying the application’s behavior. It also provides several built-in assertions that can be used to verify the response status code, headers, and body. Here are some key features of using Cypress JavaScript for API testing: HTTP Requests Cypress provides simple and intuitive APIs for making HTTP requests, allowing developers to test different API endpoints and parameters easily. It supports all common HTTP methods, such as GET, POST, PUT, DELETE, etc. Mocking and Stubbing Cypress lets you mock and stub API responses, which is useful when testing API endpoints that rely on third-party services or data sources that may not be available during development/testing. Request and Response Objects Cypress provides request and response objects that allow developers to inspect and manipulate the data being sent and received by APIs. This can help test complex APIs with nested data structures. Authentication and Authorization Cypress supports testing APIs that require authentication or authorization. Testers can use the built-in cy.request method to send authentication tokens or cookies, or they can use plugins to integrate with external services such as OAuth providers. Built-In Support for GraphQL Cypress provides built-in support for testing GraphQL APIs, including a GraphQL-request method that simplifies making GraphQL queries. API Automation Examples For API testing demo purposes, we are taking the example of the site. In the below API testing example, we are covering CRUD operation using the REST API. API Test Case: Example 1 GET Request The GET method is used to retrieve specific data and pass parameters on reading code from the API response. In the example below, we are getting specific user data using the GET method and userId. JavaScript it('GET API Automation Using GoRest API', () => { cy.request({ method: 'GET', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal('John Doe'); expect(response.body.data.gender).to.equal('male'); }); }); Output Below is an example of a POST request along with a bearer token for authorization. In POST requests, we retrieve userId for further use by passing data in the body. POST Request JavaScript describe('API Automation Using GoRest API', () => { let randomNumber =Math.floor(Math.random() * 1000); let userId; it('POST API Automation Using GoRest API', () => { const user = { name: 'John Doe', email: "johndoe123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'POST', url: 'https://gorest.co.in/public/v1/users', headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { userId=response.body.data.id expect(response.status).to.equal(201); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }) }); Output PUT Request Below is an example of a PUT request and a bearer token for authorization. In PUT requests, we update the existing data by passing the userId for which we want to update the data. JavaScript it('PUT API Automation Using GoRest API', () => { const user = { name: 'Time Cook', email: "TimCook123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'PUT', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }); Output DELETE Request The created record can be deleted using the delete method. We can pass the userId and an authorization token in the header. JavaScript it('DELETE API Automation Using GoRest API', () => { cy.request({ method: 'DELETE', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(204); }); }); Output API Automation Using ‘Cypress-Plugin-Api’ Plugin The 'cypress-plugin-api' is a useful plugin for Cypress that provides a simplified and streamlined API for making HTTP requests and performing API testing in your Cypress tests. There are several benefits of using this plugin, some of which are listed below. cy.api() command informs about the API call, such as URL, headers, response, and more to the UI frame, and this info can be viewed in time-travel snapshots. Simple table for viewing cookies. JSON data object and array folding. Color coding of methods in UI view and in the timeline. Let’s set up cypress-plugin-api. Steps 1 Install the plugin 'cypress-plugin-api' using the below commands. npm i cypress-plugin-api Or yarn add cypress-plugin-api Steps 2 Import the plugin under the path cypress/support/e2e.js file. import 'cypress-plugin-api' or require('cypress-plugin-api') package.json looks like below: JavaScript { "name": "cypress_javascript", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Kailash Pathak", "license": "ISC", "devDependencies": { "cypress": "^12.6.0" }, "dependencies": { "cypress-plugin-api": "^2.10.3" } } Add the below commands under the Cypress/e2e.js file are below: import './commands' import 'cypress-plugin-api' Let's take some examples to automate using the plugin cypress-plugin-api. Create ApiTestCaseUsingPlugin.cy.js with Methods (GET, POST, PUT, DELETE) Below is an example of Methods (GET, POST, PUT, DELETE) with their output of the execution of Test Cases. The data for Body, Response, Headers, and Cookies may be seen in the Cypress App UI in the screenshot below. The cy.api() works similarly to the cy.request() command. The benefit of using cy.api() is it provides the response, headers, and cookies are all visible, and also prints details about the call in your Cypress runner, as shown below. GET Request JavaScript it('GET API Automation Using GoRest API', () => { cy.api({ method: 'GET', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal('John Doe'); expect(response.body.data.gender).to.equal('male'); }); }); Output GET Request POST Request JavaScript it('POST API Automation Using GoRest API', () => { const user = { name: 'John Doe', email: "johndoe123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'POST', url: 'https://gorest.co.in/public/v1/users', headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { userId=response.body.data.id expect(response.status).to.equal(201); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }); Output POST Request PUT Request JavaScript it('PUT API Automation Using GoRest API', () => { const user = { name: 'Time Cook', email: "TimCook123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'PUT', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }); Output PUT Request DELETE Request JavaScript it('DELETE API Automation Using GoRest API', () => { cy.request({ method: 'DELETE', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(204); }); }); Output DELETE Request Accessibility Testing With Cypress JavaScript Accessibility testing is the process of evaluating a website or application to ensure that it can be used by people with disabilities. Cypress JavaScript provides an accessibility plugin called cypress-axe that can be used to check for accessibility issues on a website. The plugin employs the axe-core open-source library to conduct accessibility assessments, which detect accessibility infractions under the Web Content Accessibility Guidelines (WCAG). To use the cypress-axe plugin, you first need to install it using npm. Once installed, you can import the plugin into your Cypress test file and use the cy.checkA11y() method to check for accessibility violations. Executing this command triggers the axe-core library to scan the present page and disclose any accessibility problems it detects. Install the Cypress accessibility plugin by running the following command in your project directory: npm install --save-dev cypress-axe Once the installation is complete, you can import the plugin in your Cypress test file by adding the following line at the top: import 'cypress-axe'; Now, you can write your accessibility tests using the cy.injectAxe() and cy.checkA11y() commands provided by the plugin. JavaScript describe('Accessibility testing with cypress-axe', () => { it('should not have any accessibility violations', () => { cy.visit('https://ecommerce-playground.lambdatest.io/'); cy.injectAxe(); cy.checkA11y(); }); }); This test uses the cy.visit() command to open the website, and the cy.injectAxe() command to inject the accessibility plugin. The cy.checkA11y() command then checks the page for accessibility violations. Output In the screenshot, you can see nine accessibility violations. Component Testing With Cypress JavaScript Component testing is a type of software testing in which individual components of a software system are tested in isolation from the rest of the system. What Is Component Testing? The purpose of component testing is to validate the functionality and behavior of individual software modules, such as functions, classes, or methods, to ensure that they work as expected and meet the requirements specified in the design. Component testing primarily aims to uncover flaws or glitches in the software modules before their integration into the broader system, thereby minimizing the likelihood of downstream breakdowns and simplifying the process of isolating and remedying issues. By testing each component in isolation, developers can gain confidence that each module is working correctly, which can improve overall system quality and reduce the time and cost of testing and debugging. Cypress for Component Testing With Cypress, you can use its built-in testing API to create tests that target specific components of your application. Cypress JavaScript provides a <mount> command that can be used to mount a React component within a Cypress test runner, making it possible to test the component's behavior and interactions with other components. Advantages of Component Testing With Cypress JavaScript Early Detection of Defects Component testing helps detect defects or issues in the software code early in the development process. By identifying and fixing issues early, it becomes easier and cheaper to fix them than when they are detected later in the software development life cycle. Improved Reusability Component testing promotes code reusability since individual components can be reused in other applications. Faster Development Component testing helps to speed up the development process. By catching defects early, developers can fix them quickly, which leads to faster development cycles and quicker delivery of software. Better Maintainability Component testing helps to improve the maintainability of the software code. By ensuring that each component of the software is functioning correctly, it becomes easier to maintain the software in the long run. Isolation Since each component is tested in isolation, defects can be easily isolated and identified, making it easier to fix them. Example of Component Testing Before taking the example, let's do some prerequisite steps for creating component testing. Prerequisite Node is installed. VS Code is installed. Testing React components with Cypress involves setting up a testing environment and writing test cases to ensure that the components behave as expected. Here are the steps to get started: Let’s set up a React component. Step 1: Run the below command in the terminal. npx create-react-app my-new-sample-app Step 2: In the root directory and run the below command to launch React app and install Cypress (if not installed previously). cd my-new-sample-app npm start npm install cypress -D Step 3: Run the command to open Cypress runner. npx cypress open or yarn cypress open Step 4: From the below screenshot select ‘Component Testing”. Step 5: After selecting ‘Component Testing’ from the above, the below-attached screen gets opened. Select the option ‘Create React App’ from the above screen. Step 6: Click on Next step from the above screen and wait for all dependencies to install. Step 7: Click on Continue button from the above screen. Step 8: Click on Continue button from the above screen. Select the browser, e.g., Chrome, and click ‘Start Component Testing in Chrome.’ Step 9: Select ‘Create new spec’ and enter the path of the new spec and click ‘Create spec.’ Step 10: Click on Okay, run the spec button. Below is the folder structure after installing the React app. In this structure, the src folder contains all the source code for the project, while the components subfolder is where the component files are stored, and it is from these files that the test files in the components folder at the top level of the project will import the components to be tested. Each component has its folder, which includes the component file itself (component1.js, component2.js, etc.) and its corresponding test file, e.g. (component 1.test.js, component 2.test.js, etc.). Let’s create a component test case. Create a ‘counter component’ inside the src folder and give its name lambadaTest.jsx. JavaScript import { useState } from 'react' export default function Counter({ initial = 0 }) { const [count, setCount] = useState(initial) return ( <div style={{ padding: 30 }> <button style={{ color: "black", backgroundColor: "green", margin: 10 } aria-label="decrement" onClick={() => setCount(count - 1)}> - </button> <span data-cy="counter">{count}</span> <button style={{ color: "black", backgroundColor: "green", margin: 10 } aria-label="increment" onClick={() => setCount(count + 1)}> + </button> </div> ) } Let's run the created component test cases by running the below command. npx cypress open --component In the below screenshot, we can see that component tests are executed. Let’s cover some more scenarios using this Counter React component. Test Scenario Click on (+) to increment the count. Click on (-) to decrement the count. JavaScript import Counter from '../../src/lambadaTest' describe("<Counter>", () => { const counterSelector = '[data-cy="counter"]'; const incrementSelector = "[aria-label=increment]"; const decrementSelector = "[aria-label=decrement]"; it("Do two time increment then one time decrement the count ", () => { cy.mount(<Counter />); //Do two time Increment the Count cy.get(incrementSelector).click(); cy.get(incrementSelector).click(); // Put Assert cy.get(counterSelector).should("contain.text", 2); //Do the decrement now cy.get(decrementSelector).click(); // Put Assert cy.get(counterSelector).should("have.text", "1"); // Put Assert color cy.get(decrementSelector) .should("have.css", "color") .and("eq", "rgb(0, 0, 0)"); // Assert background color cy.get(decrementSelector) .should("have.css", "background-color") .and("eq", "rgb(0, 128, 0)"); }); it("Do two time decrement then one time increment the count ", () => { cy.mount(<Counter />); //Two time decrement the count cy.get(decrementSelector).click(); cy.get(decrementSelector).click(); // Assert cy.get(counterSelector).should("have.text", "-2"); //Then increment the count cy.get(incrementSelector).click(); cy.get(counterSelector).should("have.text", "-1"); // Put Assert color cy.get(decrementSelector) .should("have.css", "color") .and("eq", "rgb(0, 0, 0)"); // Put Assert background color cy.get(decrementSelector) .should("have.css", "background-color") .and("eq", "rgb(0, 128, 0)"); }); }); Output Scenario 1 In the log, first, we increment the count two times and then decrement the count once. Scenario 2 In the log, first, we decrement the count two times and then increment the count once. Mobile Responsiveness Testing With Cypress JavaScript Cypress mobile responsiveness testing is a type of testing that focuses on evaluating the responsiveness and compatibility of a website. It involves using Cypress, an end-to-end testing framework, to automate the process of simulating mobile device interactions and testing the behavior of a website or application across different screen sizes and resolutions. In Cypress JavaScript, the cy.viewport() method is used to set the dimensions of the browser's viewport. Here are some of the different methods that can be used with cy.viewport() to modify the viewport. cy.viewport() with fixed dimensions As mentioned earlier, the cy.viewport() method takes two arguments, width and height, to set the viewport dimensions. For example: cy.viewport(1280, 720) // set viewport to 1280 x 720 pixels cy.viewport() with predefined device Cypress provides several predefined device sizes that can be used with cy.viewport() to simulate different devices. For example: cy.viewport('iphone-6') // set viewport to iPhone 6 size (375 x 667 pixels) cy.viewport('ipad-mini') // set viewport to iPad Mini size (768 x 1024 pixels) cy.viewport() with custom device sizes Developers can also define custom device sizes using the cy.viewport() method. For example: cy.viewport(550, 750) // set viewport to custom size (550 x 750 pixels) cy.viewport() with landscape orientation To simulate landscape orientation, a third argument can be added to cy.viewport(). For example: cy.viewport(1024, 768, 'landscape') // set viewport to 1024 x 768 pixels in landscape orientation cy.viewport() with dynamic sizing Cypress also provides the ability to dynamically set the viewport size based on the screen size of the device running the test. For example: cy.viewport('macbook-15') // set viewport to MacBook Pro 15-inch size on a desktop computer Mobile Responsiveness Test Case Example In the below example, we are using the LambdaTest eCommerce Playground for mobile responsiveness testing. Mobile Responsiveness: Example 1 In this example, we verify that the email and password fields are visible and the label of the email and password fields. JavaScript describe('Mobile Responsiveness Testing', () => { it('Loads the login page in iphone-6', () => { cy.viewport('iphone-6') // set viewport to iPhone 6 size cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') cy.get('#input-email').should('be.visible') // check that email input is still visible cy.get('#input-password').should('be.visible') // check that password input is still visible cy.get('input[type="submit"]').should('be.visible') // check that login button is still visible cy.get('label[for="input-email"]').should('have.text', 'E-Mail Address') // check that label for email input is visible and has correct text cy.get('label[for="input-password"]').should('have.text', 'Password') // check that label for password input is visible and has correct text }) }) Code Walkthrough This code uses the viewport command to set the size of the browser window to different mobile device sizes. Then it uses the Cypress get command to check that certain elements on the page are visible and have the correct text. You can check additional tests to check other aspects of the page's responsiveness, such as how images or text scale at different screen sizes. Presented here is an instance of mobile responsiveness testing carried out on an iPhone 6. Similarly, responsiveness testing can be performed on other viewports by modifying the device name in the cy.viewport() method. Output Below is the output of the above-executed test case. Mobile Responsiveness: Example 2 Below is an example for searching the text in the LambdaTest eCommerce Playground for mobile responsiveness testing. Code Walkthrough This code uses the viewport (iphone-x) in beforeEach() hook. In the test case, we are searching the text ‘Apple’ and verify the text after searching. beforeEach() is a hook that runs a block of code before each test case within a test suite. JavaScript describe.only('Mobile Responsiveness Test for search the text in E-commerce Playground Site', () => { beforeEach(() => { cy.viewport('iphone-x') cy.visit('https://ecommerce-playground.lambdatest.io/') }) it('Searches for the text "Apple" and displays results', () => { // Enter search data and submit form cy.get('[name="search"]').eq(1).click({force: true}).type('Apple') cy.get('.type-icon').click() // Verify search results cy.url().should('include', 'search=Apple') cy.contains('Search - Apple').should('be.visible') cy.get('.product-layout').should('have.length.gt', 0) }) }) Output Below is the output of the above-executed test case. Running Cypress Test Cases Locally You can run the test case from the command line or use Cypress runner. Let’s run test cases using Cypress runner. To open the Cypress test runner, run the following command. yarn run cypress open The above command will open the Cypress test runner with the existing test cases. You can select the browser on which you want to run the test case. Let’s execute three test cases in the local environment. LoginAndSubscribeForNewsletter.cy.js, MobileResponsivenessTesting.cy.js, and SearchWithValid_InvalidData.cy.js File Name: LoginAndSubscribeForNewsletter.cy.js JavaScript describe('Login and Subscribe', () => { it('Logs in and subscribes to newsletter', () => { // Visit the site cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') // Click the login button cy.get('[value="Login"]').click() // Enter valid email and password cy.get('#input-email').type('lambdatestnew@yopmail.com') cy.get('#input-password').type('Lambda123') // Click the login button cy.get('[value="Login"]').click() // Verify successful login cy.url().should('include', 'index.php?route=account/account') cy.contains('My Account').should('be.visible') // Subscribe to newsletter cy.contains('Newsletter').click() cy.get('#input-newsletter-yes').click({force:true}) cy.get('[value="Continue"]').click() // Wait for subscription success message cy.get('.alert-success').should('contain', 'Success: Your newsletter subscription has been successfully updated!') }) }) File Name: MobileResponsivenessTesting.cy.js describe('Mobile Responsiveness Testing', () => { it('Loads the login page in iphone-6', () => { cy.viewport('iphone-6') // set viewport to iPhone 6 size cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') cy.get('#input-email').should('be.visible') // check that email input is still visible cy.get('#input-password').should('be.visible') // check that password input is still visible cy.get('input[type="submit"]').should('be.visible') // check that login button is still visible cy.get('label[for="input-email"]').should('have.text', 'E-Mail Address') // check that label for email input is visible and has correct text cy.get('label[for="input-password"]').should('have.text', 'Password') // check that label for password input is visible and has correct text }) File Name: SearchWithValid_InvalidData.cy.js describe('Login Test', () => { beforeEach(() => { cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') }) it('Searches for the text "Apple" and verify result', () => { // Enter search data and submit form cy.get('[name="search"]').eq(0).type('Apple') cy.get('.type-text').click() // Verify search results cy.url().should('include', 'search=Apple') cy.contains('Search - Apple').should('be.visible') cy.get('.product-layout').should('have.length.gt', 0) }) it('Displays message with no search results for invalid search term', () => { // Enter search term that returns no results and submit form cy.get('[name="search"]').eq(0).type('abc') cy.get('.type-text').click() // Verify message for no search results cy.contains('There is no product that matches the search criteria.').should('be.visible') }) }) Let's execute the above test cases. When we run the command yarn run cypress open, the below screen is opened. Test Execution LoginAndSubscribeForNewsletter.cy.js MobileResponsivenessTesting.cy.js SearchWithValid_InvalidData.cy.js Wrapping Up In conclusion, Cypress test automation using JavaScript can significantly improve the speed and efficiency of software testing. As JavaScript supports many testing frameworks, developers can leverage it. Also, JavaScript is very easy to learn. Developers can learn JavaScript very fast and start development.
Are you ready to learn how to create a choropleth map with JavaScript like a boss? Maps are a powerful way to visualize data, but building one from scratch can be a daunting task. Don't worry, I'm here to help! In this tutorial, I'll guide you through the process of building a JavaScript choropleth map step by step. And to make things more interesting, we’ll use Queen Elizabeth II's state visits as an example to showcase the power of this type of map. Get ready to impress yourself and your audience with stunning data visualizations, and let's get started on this exciting journey together! What Is a Choropleth Map? A choropleth map is a type of thematic map that uses color to represent values of a specific quantity within different regions or geopolitical areas. The map is divided into areas or polygons, and the color of each area represents the value of the data being visualized. Typically, darker shades of color indicate higher values, while lighter shades represent lower values. So, as I mentioned, in this tutorial, we'll be creating a JS-based choropleth map to showcase Queen Elizabeth II's state visits around the world. By using different colors to represent the number of visits in each region, we can easily identify which areas the Queen visited the most frequently. The final interactive choropleth map will look like this: How To Build a Basic Choropleth Map Creating a choropleth map with JavaScript may seem challenging, but fear not! There are just four simple steps to follow: Set up an HTML page to display the map. Add all the necessary JavaScript files. Include the data. Write the required JavaScript code to render the map. Once you've completed these four steps, you'll have a beautiful choropleth map that accurately represents your data. So, let's dive into each step in more detail and learn how to easily create a stunning JavaScript choropleth map! 1. Set up an HTML Page To Display the Choropleth Map To start creating the choropleth map, the first step is to set up an HTML page with a div element and give it a unique ID to reference it later. I’ll set the width and height of the div element to 100% to render the map on the whole page, but you can adjust these values to suit your needs. This div element will be the container for the chart, and the ID will be used to reference it in the JavaScript code. So, let's get started by creating the HTML page for the upcoming choropleth map! HTML <html> <head> <title>JavaScript Choropleth Map</title> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="container"></div> </body> </html> 2. Add All the Necessary JavaScript Files To easily create data visualizations for the web, I use a JavaScript charting library with in-built utilities and functions. With the vast number of such JS libraries available, the process for creating charts and maps is similar in essence and logic across most of them. In this tutorial, I am using the AnyChart JS charting library as it is beginner-friendly, with extensive documentation and plenty of examples to help you get started. It is needed to link the required JS scripts in the <head> section of the HTML page. These scripts include the Core and Geo Maps modules and the file that contains geodata for the world map, all of which are available on the CDN. I'll also use two more script files to connect the data to the choropleth map. One is the Data Adapter, which will help load the data, and the other is Proj4js, a JavaScript library that will transform the coordinates from one coordinate system to another so the data can be plotted on the map accurately. HTML <html> <head> <title>JavaScript Choropleth Map</title> <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-base.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-map.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.0/geodata/custom/world/world.js"></script> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="container"></div> <script> // All the code for the JS choropleth map will come here </script> </body> </html> 3. Include the Data Alright, let's get the data loaded up and plotted on the JS choropleth map! First things first, I have collected the data on the state visits made by Queen Elizabeth II from Wikipedia and put it in a JSON file for easy access. You can find it here. All the necessary files are already linked in the <head> section of the HTML page. So it’s possible to use the loadJsonFile() method inside a <script> tag in the body of the page to load the JSON file containing the Queen's state visits data. JavaScript anychart.data.loadJsonFile(<fileURL>, function (data) {}); Great! Now that everything is set up and ready to go, it's time to move on to the main part of the journey: creating the choropleth map visualization! Let's do this! 4. Write the Required JavaScript Code To Render the Map You can get the choropleth map rendered with just a few lines of JS code. First, make sure that all the code is inside the function anychart.onDocumentReady() so that the page is fully loaded before executing anything else. Once the data file is loaded, set the data, create the map, and set the geodata. JavaScript <script> anychart.onDocumentReady(function() { // load data from a json file anychart.data.loadJsonFile('https://gist.githubusercontent.com/shacheeswadia/8f45da54d9bf2032fee201dbfc79e0e4/raw/5d10d58f40b4a1d994cef36dbc64545ef90ead80/queenVisits.json', function (data) { // create a dataset let dataSet = anychart.data.set(data); // create a map instance let map = anychart.map(); // set the geodata map.geoData("anychart.maps.world"); // the rest of the JS code will be here }); }); </script> Now, create a series with the choropleth() function and the loaded data. For the coloring, let’s set up a linear color scale with four different shades of blue based on the colors on the royal website. As per convention, the darker the shade of a country, the higher the number of visits to that region. JavaScript // create a choropleth series let series = map.choropleth(dataSet); // set the map colors series .colorScale( anychart.scales.linearColor("#f2f2f2", "#42a5f5", "#1976d2", "#233580") ); Finally, give a title to the map, set the container, and draw the resulting map. JavaScript // set the map title map.title("State Visits Made by Queen Elizabeth II"); // set the container map.container('container'); // initiate the map drawing map.draw(); And voila! A beautiful and functional choropleth map is ready! Check out how it looks. You can find the entire code below or view and play with it here. HTML <html> <head> <title>JavaScript Choropleth Map</title> <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-base.min.js"></script> <script data-fr-src="https://cdn.anychart.com/releases/8.11.0/js/anychart-map.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.0/geodata/custom/world/world.js"></script> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="container"></div> <script> anychart.onDocumentReady(function () { // load data from a json file anychart.data.loadJsonFile( "https://gist.githubusercontent.com/shacheeswadia/8f45da54d9bf2032fee201dbfc79e0e4/raw/5d10d58f40b4a1d994cef36dbc64545ef90ead80/queenVisits.json", function (data) { // create a dataset let dataSet = anychart.data.set(data); // create a map instance let map = anychart.map(); // set the geodata map.geoData("anychart.maps.world"); // create a choropleth series let series = map.choropleth(dataSet); // set the map colors series .colorScale( anychart.scales.linearColor("#f2f2f2", "#42a5f5", "#1976d2", "#233580") ); // set the map title map.title("State Visits Made by Queen Elizabeth II"); // set the container map.container("container"); // initiate the map drawing map.draw(); } ); }); </script> </body> </html> How to Customize a Choropleth Map The existing choropleth map is already impressive, but let me show you how to make it even more insightful and engaging! In the following steps, I'll demonstrate how you can customize the choropleth map to add more visual appeal and interactivity: Modify the colors by hovering over map areas. Add a color legend. Improve the tooltip and the title format. Add zoom controls. 1. Modify the Colors by Hovering Over the Areas You can make the map more visually appealing by modifying the colors of the hovered regions. By default, hovered regions become gray, but you can change this to a darker shade of the base color to make it more intuitive. This can be achieved by changing the fill color of the region when it is hovered and using the darken() function. JavaScript series .hovered() .fill(function (d) { return anychart.color.darken(d.sourceColor, 0.2); }); 2. Add a Color Legend Adding a color legend to your choropleth map is always a great idea, as it can help your audience understand the values and colors used in the map. To add a color legend, you only need to enable the color range. This will automatically create a legend that represents what color indicates what value. By default, the color range is disabled. To enable it, you just need to add a few lines of code: JavaScript map.colorRange() .enabled(true); And voila! You now have a beautiful and informative color legend to complement your choropleth map. 3. Improve the Tooltip and Title Format Let's take the choropleth map to the next level by improving the tooltip and the title format. Tooltips are a powerful way to showcase additional information, and they can be made even better by enabling HTML for customizing the tooltip and formatting the value. I thought it would be nice to add a custom text and indicate how many times the Queen visited the hovered country. Additionally, the specific case of the United Kingdom can be handled to showcase that the Queen lived there. With these changes, the tooltip is not only more informative but also more visually appealing. The users will enjoy the playful tone of the tooltip while learning interesting facts about the Queen's travels. JavaScript series .tooltip() .useHtml(true) .format(function (d) { if (d.name == "United Kingdom") { return "<h6 style='font-size:14px; font-weight:400; margin: 0.2rem 0;'>The Queen lived here.</h6>"; } else { return ( "<h6 style='font-size:14px; font-weight:400; margin: 0.2rem 0;'>The Queen visited <b>" + d.value + "</b> times.</h6>" ); } }); In addition to customizing the tooltip, why not enhance the chart title using HTML? For example, a subtitle will provide more context, and custom text styling will make it all shine. JavaScript map .title() .enabled(true) .useHtml(true) .text( '<span style="color: #c8102e; font-size:18px;">State visits made by Queen Elizabeth II</span>' + <span style="font-size: 15px;">(The Queen is the most widely travelled head of state in history)</span>' ); 4. Add Zoom Controls Adding zoom controls to a world map can be a great way to allow users to zoom into specific areas of interest. To get started, add the required scripts and CSS links to your HTML document's <head> section. Once you have done this, you can create the zoom controls and render them on the map with just a few lines of code. HTML <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-exports.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-ui.min.js"></script> <link rel="stylesheet" type="text/css" href="https://cdn.anychart.com/releases/8.11.0/css/anychart-ui.min.css"> <link rel="stylesheet" type="text/css" href="https://cdn.anychart.com/releases/8.11.0/fonts/css/anychart-font.min.css"> JavaScript var zoomController = anychart.ui.zoom(); zoomController.render(map); It's worth noting that the zoom feature can be particularly useful when working with large and detailed choropleth maps. In this case, it can be a great way to allow users to explore specific regions or areas of interest in more detail. And now, just sit back and admire your beautiful JavaScript choropleth map! With the addition of customized colors, a color legend, improved tooltips and titles, and zoom controls, it’s an interactive and engaging visualization that is sure to impress. See the complete code for this project below, and feel free to play with it and see the choropleth map’s full interactive version live here. HTML <html> <head> <title>JavaScript Choropleth Map</title> <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-base.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-map.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.0/geodata/custom/world/world.js"></script> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="container"></div> <script> anychart.onDocumentReady(function () { // load data from a json file anychart.data.loadJsonFile( "https://gist.githubusercontent.com/shacheeswadia/8f45da54d9bf2032fee201dbfc79e0e4/raw/5d10d58f40b4a1d994cef36dbc64545ef90ead80/queenVisits.json", function (data) { // create a dataset let dataSet = anychart.data.set(data); // create a map instance let map = anychart.map(); // set the geodata map.geoData("anychart.maps.world"); // create a choropleth series let series = map.choropleth(dataSet); // set the map colors series.colorScale( anychart.scales.linearColor("#f2f2f2", "#42a5f5", "#1976d2", "#233580") ); // customize the colors in the hovered state series.hovered().fill(function (d) { return anychart.color.darken(d.sourceColor, 0.2); }); // create the map legend map.colorRange().enabled(true); // create zoom controls let zoomController = anychart.ui.zoom(); zoomController.render(map); // customize the tooltip text series .tooltip() .useHtml(true) .format(function (d) { if (d.name == "United Kingdom") { return "<h6 style='font-size:14px; font-weight:400; margin: 0.2rem 0;'>The Queen lived here.</h6>"; } else { return ( "<h6 style='font-size:14px; font-weight:400; margin: 0.2rem 0;'>The Queen visited <b>" + d.value + "</b> times.</h6>" ); } }); // set the map title map .title() .enabled(true) .useHtml(true) .text( '<span style = "color: #c8102e; font-size:18px;">State Visits Made by Queen Elizabeth II</span>' + '<br/><span style="font-size: 15px;">The Queen is the most widely traveled head of state in history</span>' ); // set the container map.container("container"); // initiate the map drawing map.draw(); } ); }); </script> </body> </html> Conclusion Сhoropleth maps are a powerful tool for visualizing data on a geographic scale. Using JavaScript, you can create stunning interactive ones that communicate complex information in a clear and concise manner. Whether you're tracking the travels of a monarch or visualizing the spread of a pandemic, the possibilities are endless. So, let your imagination run wild and start exploring the world through the lens of data!
At Solution Street, we love helping our clients solve their business problems through technology. When it comes to building responsive, dynamic web applications, we are big fans of the React ecosystem. React is a popular JavaScript framework that was developed by Facebook and has gained a massive following among developers due to its simplicity, modularity, and reusability. In this series of short “bloglets” our team will cover a wide array of React topics, including developer tips, issues, and experiences. We are hopeful that everyone from the beginner to the seasoned professional will find something useful in each post. And if you’re looking to build an amazing web application, we at Solution Street would enjoy talking with you and seeing how we can help! React Candidates: Beware These Common Mistakes! By Jeff Schuman At Solution Street, we hire great candidates with all manner of skills around the Software Development Life Cycle. In the process, we interview front-end developers and, quite often, React developers. Our process involves the candidate doing a (gentle) live-coding exercise. We do our best to make the candidate comfortable and set them up for success, presenting a well-thought-out, descriptive exercise, enabling them to use their own development machine/IDE, and providing encouragement and positive feedback. Having conducted these interviews for years, I thought it would be good to share some common mistakes candidates make during this coding exercise: Not Understanding .Map() And How To Use It When Generating React Components Understanding the JavaScript .map() function is key. In its simplest form, the .map() function iterates over a JavaScript array and returns a new array one for each item in the original array: In the context of React components, .map() is commonly used to iterate over a list of objects and render a component for each of the items in the list. Take a look at its sample use in the Catalog component below when rendering each CatalogItem: Not Breaking the Problem Down Into Components This one is (admittedly) a bit subjective. An interview is a candidate’s chance to demonstrate their expertise and experience with React, including an understanding of its component-based approach to User Interfaces. When presented with our exercise, we make clear that we are interested in seeing how the candidate “breaks down” the problem into appropriate components. However, some candidates solve our exercise by adding ALL of their code to our top-level App.js component. While this is certainly a viable solution, it doesn’t demonstrate the candidate’s ability to separate concerns, manipulate state, pass props, etc. It’s a missed opportunity for the candidate. Misunderstanding State Manipulation Regardless of whether you use class-based components or functional components, there is a key tenet with regard to state management in a component: You should not change objects that you hold in the React state directly. This is where knowledge of the .map() function — see above — or spread syntax is helpful. See the example(s) below. Not good: Better: In conclusion, we’re big fans of React at Solution Street. When we interview candidates, we enjoy discussing their experience and joy in using the React framework to build sophisticated web applications. As a candidate, it is important to know both the fundamental concepts of React AND the common idioms when designing a React application. Reduce Redundant React Re-Renders, Really! By Jared Mohney As developers, despite our most wonderful efforts, sometimes our components re-render more often than they need to. This leads to unnecessary work for our user’s browser, negatively affecting their experience! Today we’ll quickly look at two hooks that React provides — useMemo and useCallback — that can help ease these woes. useMemo The useMemo hook memoizes a value: re-computing it only when its dependencies change. This can be useful when calculating a value that takes a long time to compute (async, computationally expensive, etc.) or is used in multiple places within a component. Here with useMemo, we will not recompute expensiveToComputeValue unless a or b has changed, protecting our user from a poor experience. useCallback The useCallback hook memoizes a function, re-creating it only when its dependencies change. This can be useful when passing a function down to a child component that relies on referential equality to prevent unnecessary re-renders. Here when MyComponent re-renders, handleClick is not re-created, thereby not affecting any components downstream of it (button). TIP: As with all things, moderation! Profile your page performance and ensure you’re seeing gains worth the added complexity. Conclusion Unnecessary re-rendering is a solved problem. With a close eye and purposeful application, useMemo and useCallback stand to make our applications more performant and enjoyable to use. React Portals: When You Need a React Component To Render Somewhere Else By Adam Boudion Every once in a while, in front-end development, we find ourselves needing something to be on top of everything else. Toast messages, modals, and tooltips are the most common examples of this. Sounds conceptually pretty simple, right? We just find a way to make sure it has the biggest z-index. But sometimes, weird stuff happens. Consider the following code: Seems simple enough. We press a button, and we get a toaster message. Easy Peasy. Let’s run it and see the result. Wait a second…What’s it doing under there? … Ah, looks like the header has an enormous z-index, and it’s interfering with the appearance of our toaster. Now, in this simplistic example, we could just refactor a little bit to squash this bug, but in more complex codebases, this type of refactoring can carry risks of causing regressions or introducing new issues. So what’s a developer to do? Enter the React Portal. React Portals allow you to render child components outside of their parent’s DOM hierarchy while preserving all of its standard react behaviors and relationships, such as props and context. This is because a Portal can “move” a child component to be a descendant of any viable node in the DOM tree while having it be unchanged in the React tree. Let’s make a little change to our toaster's render method. The createPortal method takes in two arguments. The first argument is any valid renderable React child, and the second argument is any valid DOM element that you wish to become the new parent of the first argument in the DOM. Perfect! Now it visually “pops out” as we intended. As we can see, the toaster is also rendered as a child of the body in the DOM. Though not appropriate in all situations, portals were added to React for use cases just like this to add a powerful tool in the battle against rogue styling issues.
Anthony Gore
Founder,
Vue.js Developers
John Vester
Lead Software Engineer,
Marqeta @JohnJVester
Justin Albano
Software Engineer,
IBM
Swizec Teller
CEO,
preona