使用Storybook和Cypress测试角度分量@Output

如何解决使用Storybook和Cypress测试角度分量@Output

我正在尝试测试角度分量的输出。

我有一个复选框组件,该组件使用EventEmitter输出其值。复选框组件包装在一个故事书中,用于演示和测试:

export const basic = () => ({
  moduleMetadata: {
    imports: [InputCheckboxModule],},template: `
<div style="color: orange">
 <checkbox (changeValue)="changeValue($event)" [selected]="checked" label="Awesome">
 </checkbox>
</div>`,props: {
    checked: boolean('checked',true),changeValue: action('Value Changed'),});

我正在使用一项操作来捕获值更改并将其记录到屏幕上。

但是,当为此组件编写赛普拉斯e2e时,我仅使用iFrame,而不是整个故事书应用程序。

我想找到一种测试输出是否正常的方法。我尝试对iFrame中的postMessage方法使用间谍,但这不起作用。

 beforeEach(() => {
      cy.visit('/iframe.html?id=inputcheckboxcomponent--basic',{
        onBeforeLoad(win) {
          cy.spy(window,'postMessage').as('postMessage');
        },});
    });

,那么断言将是:

  cy.get('@postMessage').should('be.called');

还有其他方法可以断言(changeValue)="changeValue($event)" 开除了?

解决方法

方法 1:模板

我们可以将最后发出的值绑定到模板并检查它。

{
  moduleMetadata: { imports: [InputCheckboxModule] },template: `
   <checkbox (changeValue)="value = $event" [selected]="checked" label="Awesome">
   </checkbox>
  
   <div id="changeValue">{{ value }}</div> <!-- ❗️? -->
 `,}
it("emits `changeValue`",() => {
 // ...

 cy.get("#changeValue").contains("true"); // ❗️?
});

方法 2:窗口

我们可以将最后发出的值分配给全局 window 对象,在 Cypress 中检索它并验证该值。

export default {
  title: "InputCheckbox",component: InputCheckboxComponent,argTypes: {
    selected: { type: "boolean",defaultValue: false },label: { type: "string",defaultValue: "Default label" },},} as Meta;


const Template: Story<InputCheckboxComponent> = (
  args: InputCheckboxComponent
) =>
  ({
    moduleMetadata: { imports: [InputCheckboxModule] },props: args,} as StoryFnAngularReturnType);


export const E2E = Template.bind({});
E2E.args = {
  label: 'E2e label',selected: true,changeValue: value => (window.changeValue = value),// ❗️?
};

it("emits `changeValue`",() => {
  // ...

  cy.window().its("changeValue").should("equal",true); // ❗️?
});

方法 3:角度

我们可以使用存储在 ng 下的全局命名空间中的 Angular's functions 来获取对 Angular 组件的引用并监视输出。

⚠️注意:

  • ng.getComponent() 仅在 Angular 在开发模式下运行时可用。 IE。 enableProdMode() 没有被调用。
  • process.env.NODE_ENV = "development"; 中设置 .storybook/main.js 以防止 Storybook 在生产模式下构建 Angular(请参阅 source)。
export const E2E = Template.bind({});
E2E.args = {
  label: 'E2e label',// Story stays unchanged
};

describe("InputCheckbox",() => {
  beforeEach(() => {
    cy.visit(
      "/iframe.html?id=inputcheckboxcomponent--e-2-e",registerComponentOutputs("checkbox") // ❗️?
    );
  });

  it("emits `changeValue`",() => {
    // ...

    cy.get("@changeValue").should("be.calledWith",true); // ❗️?
  });
});
function registerComponentOutputs(
  componentSelector: string
): Partial<Cypress.VisitOptions> {
  return {
    // https://docs.cypress.io/api/commands/visit.html#Provide-an-onLoad-callback-function
    onLoad(win) {
      const componentElement: HTMLElement = win.document.querySelector(
        componentSelector
      );
      // https://angular.io/api/core/global/ngGetComponent
      const component = win.ng.getComponent(componentElement);

      // Spy on all `EventEmitters` (i.e. `emit()`) and create equally named alias
      Object.keys(component)
        .filter(key => !!component[key].emit)
        .forEach(key => cy.spy(component[key],"emit").as(key)); // ❗️?
    },};
}

总结

  • 我喜欢在方法 1 中没有魔法。它易于阅读和理解。不幸的是,它需要指定一个模板,其中包含用于验证输出的附加元素。
  • 方法 2 的优点是我们不再需要指定模板。但是我们需要为每个要测试额外代码的 @Output 添加。此外,它使用全局 window 来“通信”。
  • Apprach 3 也不需要模板。它的优点是 Storybook 代码(story)不需要任何调整。我们只需要将一个参数传递给 cy.visit()(很可能已经被使用过)以便能够执行检查。因此,如果我们想通过 Storybook 的 iframe 测试更多组件,这感觉就像是一个可扩展的解决方案。最后但并非最不重要的是,我们检索对 Angular 组件的引用。有了这个,我们还可以直接在组件本身上调用方法或设置属性。这与 ng.applyChanges 的结合似乎为其他测试用例打开了一些大门。
,

您正在监视window.postMessage(),这是一种在窗口对象(弹出窗口,页面,iframe,...)之间实现跨域通信的方法。

Storybook中的iFrame不会将任何消息传递给另一个窗口对象,但是您可以在应用程序上安装Kuker或另一个外部Web调试器来监视两者之间的消息,从而使Cypress间谍方法起作用。

如果您选择在角度应用程序上安装Kuker,请按以下步骤操作:

npm install -S kuker-emitters

还要添加Kuker Chrome扩展程序以使其正常工作。

,

如果您使用 cypress-storybook 包和 @storybook/addon-actions,有一种方法可用于此用例,我认为它提供了最简单的解决方案。

使用 Storybook-actions 插件,您可以像这样声明 @Output 事件

export default {
    title: 'Components/YourComponent',component: YourComponent,decorators: [
        moduleMetadata({
            imports: [YourModule]
        })
     ]
 } as Meta;

const Template: Story<YourStory> = (args: YourComponent) => ({
   props: args
 });

export const default = Template.bind({});
default.args = {
  // ...
  changeValue: action('Value Changed'),// action from @storybook/addon-actions
};

在您的 cypress 测试中,您现在可以调用 cy.storyAction() 方法并对它应用 expect 语句。

it('should execute event',() => {
  // ...
  cy.storyAction('Value Changed').should('have.been.calledWith','value');
})

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-