工作中使用jasmine遇到的一个html element和Component绑定属性失去同步的问题
background
file location: projectsstorefrontlibsrccms-componentscheckoutcomponentsdelivery-modedelivery-mode.component.html
we are now talking about property binding of “continue” button’s disabled property.
Expected behavior
when component property deliveryModelnvalid = true, continue button’s disabled property will be set as true as well, so button is disabled, and vice versa.
Below is the getter implementation of deliveryModeInvalid in Component:
so if we set this.mode.controls[‘deliveryModeId’] to ‘’ or null, deliveryModeInvalid will equal to true.
Jerry’s observation in unit test
if we change the value of this.mode.controls[‘deliveryModeId’] TWICE in the same test spec, [disabled] and deliveryModeInvalid will LOSE synchronization, even fixture.detectChanges is called manually.
See my test below.
Paste the following source code to replace your delivery-mode.component.spec.ts and launch it:
import { Component } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import {
CheckoutDeliveryService,
DeliveryMode,
I18nTestingModule,
} from '@spartacus/core';
import { Observable, of } from 'rxjs';
import { CheckoutConfigService } from '../../services/checkout-config.service';
import { CheckoutStepService } from '../../services/checkout-step.service';
import { DeliveryModeComponent } from './delivery-mode.component';
import createSpy = jasmine.createSpy;
import { LoaderState } from '../../../../../../core/src/state/utils/loader';
import { By } from '@angular/platform-browser';
@Component({
selector: 'cx-spinner',
template: '',
})
class MockSpinnerComponent {}
class MockCheckoutDeliveryService {
loadSupportedDeliveryModes = createSpy();
setDeliveryMode = createSpy();
getSupportedDeliveryModes(): Observable<DeliveryMode[]> {
return of();
}
getSelectedDeliveryMode(): Observable<DeliveryMode> {
return of();
}
getLoadSupportedDeliveryModeProcess(): Observable<LoaderState<void>> {
return of();
}
}
class MockCheckoutConfigService {
getPreferredDeliveryMode(): string {
return '';
}
}
class MockCheckoutStepService {
next = createSpy();
back = createSpy();
getBackBntText(): string {
return 'common.back';
}
}
const mockActivatedRoute = {
snapshot: {
url: ['checkout', 'delivery-mode'],
},
};
describe('DeliveryModeComponent', () => {
let component: DeliveryModeComponent;
let fixture: ComponentFixture<DeliveryModeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, I18nTestingModule],
declarations: [DeliveryModeComponent, MockSpinnerComponent],
providers: [
{
provide: CheckoutDeliveryService,
useClass: MockCheckoutDeliveryService,
},
{ provide: CheckoutStepService, useClass: MockCheckoutStepService },
{ provide: CheckoutConfigService, useClass: MockCheckoutConfigService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DeliveryModeComponent);
component = fixture.componentInstance;
console.log('----------- a new test comes ------------------');
});
describe('continue button', () =>{
const getContinueBtn = () => fixture.debugElement.query(By.css('.cx-checkout-btns .btn-primary'));
const setDeliveryModeId = (value: string) =>
component.mode.controls['deliveryModeId'].setValue(value);
function setDeliveryModeIdNull(){
setDeliveryModeId(null);
fixture.detectChanges();
trace(null);
}
function setDeliveryModeIdValid(){
setDeliveryModeId('a');
fixture.detectChanges();
trace('a');
}
function trace(id){
const button = getContinueBtn();
console.log('************** Delivery Mode id is set as: ' + id + '**************');
console.log('Flag component.deliveryModeInvalid: ' +
component.deliveryModeInvalid + ' button.disabled: ' + button.nativeElement.disabled );
}
function valid_then_null(){
setDeliveryModeIdValid();
setDeliveryModeIdNull();
}
function null_then_valid(){
setDeliveryModeIdNull();
setDeliveryModeIdValid();
}
it('first valid then null', () => {
valid_then_null();
expect(component).toBeTruthy();
});
it('null then valid', () => {
null_then_valid();
expect(component).toBeTruthy();
});
it('only valid', () => {
setDeliveryModeIdValid();
expect(component).toBeTruthy();
});
it('only null', () => {
setDeliveryModeIdNull();
expect(component).toBeTruthy();
});
});
});
In this unit test file I construct four test specs:
(1) set delivery mode id to a valid value first, then set null; (2) set delivery mode id to null first, then set a valid value to it; (3) only set a valid value; (4) only set null;
In each test spec, once I manupulate the value of this.mode.controls[‘deliveryModeId’], then use console.log to display the following pair of values:
- component.deliveryModeInvalid
- button.nativeElement.disabled
In theory the two must always be equal.
Test result
Conclusion
Avoid change delivery mode id TWICE in a single test spec. The case to set id as A in beforeEach and set id as B in a test spec SHOULD also be considered as TWICE, so the two value will lose synchronization as well.
- 左手用R右手Python系列——任务进度管理
- 左右用R右手Pyhon系列——趣直播课程抓取实战
- 第三周编程作业-Planar data classification with one hidden layerPlanar data classification with one hidden l
- 用R语言照葫芦画瓢撸了一个简易代理~
- 左手用R右手Python——CSS网页解析实战
- R语言数据抓取实战——RCurl+XML组合与XPath解析
- Python网络数据抓取实战——Xpath解析豆瓣书评
- 左手用R右手Python系列17——CSS表达式与网页解析
- 左手用R右手Python系列16——XPath与网页解析库
- 扒一扒rvest的前世今生!
- RCurl中这么多get函数,是不是一直傻傻分不清!!!
- 机器学习(二)深度学习实战-使用Kera预测人物年龄问题描述引入所需要模块加载数据集创建模型编译模型优化optimize1 使用卷积神经网络optimize2 增加神经网络的层数输出结果结果
- 异步加载的基本逻辑与浏览器抓包一般流程
- 左手用R右手Python系列之——表格数据抓取之道
- HTML 教程
- HTML 简介
- html div 标签介绍
- html span 标签介绍
- html a 超链接标签
- HTML Br换行标签介绍
- HTML P段落标签介绍
- HTML br与p标签区别
- Html H 标题标签
- html px em pt长度单位
- HTML form 标签
- HTML radio 单选框
- HTML B 加粗标签
- HTML strong加粗粗体标签
- HTML em 强调标签
- HTML i 斜体标签
- HTML u下划线标签
- HTML s 删除线标签
- Html img 图片标签
- Html上标注sup与下标注sub标签
- HTML nobr 禁止换行标签
- HTML hr 水平线标签
- HTML label 标签
- HTML input 标签
- HTML textarea 标签
- HTML select下拉列表标签
- HTML checkbox 多选框
- HTML font color 标签
- HTML iframe 框架标签
- HTML Table 表格
- HTML dl dt dd 标签
- HTML ol li有序列表标签
- HTML ul li 无序列表标签
- HTML 注释
- CSS 教程
- CSS 简介
- CSS 语法
- CSS Id 和 Class选择器
- CSS 样式的创建
- CSS background 背景介绍
- CSS 文本样式
- CSS font 字体
- CSS A 链接
- CSS ul ol列表样式
- CSS TABLE 样式
- CSS 框模型
- CSS border 边框
- CSS Outlines 轮廓
- CSS 外边距 Margin
- CSS Padding 内边距
- CSS 分组和嵌套选择器
- CSS 尺寸 (Dimension)
- CSS Display 属性
- CSS Position 定位
- CSS Float 浮动
- CSS 水平对齐(Horizontal Align)
- CSS 组合选择符
- CSS 伪类
- CSS 伪元素
- CSS 导航栏
- CSS 下拉菜单
- CSS 图片廊
- CSS 图像透明/不透明
- CSS sprite 图像拼合技术
- CSS 媒体类型
- CSS 属性选择器
- CSS 实例
- 计算CNN卷积神经网络中各层的参数数量「附代码」
- C++ 万字长文第一篇---拿下字节面试
- 家国梦自动收取金币、货物、升级建筑、拆相册等脚本
- matplotlib 设置移动边框
- 发布你的第一个nodejs c++插件
- nodejs多线程的探索和实践
- 3分钟短文 | Laravel 检验关联模型是否存在的2个必知必会方法
- python读取ini配置的类封装
- ESP32蓝牙的Gatt Client的例子演练
- 3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?
- 3分钟短文 | Laravel 内3种数据校验的写法,你喜欢哪一个?
- 纯JavaScript实现的MQTT智能门锁
- 3分钟短文 | Laravel 灵活地获取当前请求的路由地址
- 云原生安全 | docker容器逃逸
- 字节数组X中存放着 0~F共16个十六进制数,请将这些数以十六进制形式显示在屏幕上。