Skip to content

Commit

Permalink
Add v1: Mount plain html elements
Browse files Browse the repository at this point in the history
  • Loading branch information
klovaaxel committed Aug 3, 2024
1 parent 9498e10 commit 111fe70
Show file tree
Hide file tree
Showing 17 changed files with 3,026 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
*.tsbuildinfo
dist
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Cypress Component Testing for HTML elements

<p align="center">
<img width="250" height="250" src="https://upload.wikimedia.org/wikipedia/commons/6/61/HTML5_logo_and_wordmark.svg">
</p>

> "Use all the power of cypress component testing with simple html elements and web components. ⚡"
This package provides configuration and commands for testing html elements with the power of cypress.

## Getting started

To install, run:

```bash
npm install -D cypress-ct-html
```

Once you have the package installed alongside Cypress, you can run `npx cypress open`, choose "Component Testing", and HTML should appear in the list of frameworks available.

Learn more about [third-party definitions](https://docs.cypress.io/guides/component-testing/third-party-definitions)

## Configuration

Add `cypress-ct-html` framework to your `cypress.config.{ts,js}` file

```ts
export default defineConfig({
component: {
devServer: {
framework: "cypress-ct-html",
// more config here
},
},
});
```

If you're using TypeScript, you may get a type error when setting the framework property. If so, you'll need to typecast it as `any`

```ts
framework: 'cypress-ct-html' as any,
```

## Adding mount Command

Next, add the following lines to your `component.{js.ts}`

```ts
import { mount } from "cypress-ct-html";

Cypress.Commands.add("mount", mount);
```

Optionally, this package brings its custom types definitions. Add the following to `tsconfig.json` or `jsconfig.json` in your project.

```json
{
"compilerOptions": {
// more compiler options...
"types": ["cypress", "cypress-ct-html"]
}
}
```

## Usage notes

You can now mount any html element in a component test, for example:

```ts
it("should display content", () => {
const div = document.createElement("div");
div.textContent = "Hello, World!";
cy.mount(div);

cy.get("#content").should("contain.text", "Hello, World!");
});
```

Or find content inside your web component

```ts
import "path/to/my-element";

it("should render its children", () => {
const myElement = document.createElement("my-element");
cy.mount(myElement);

cy.get("my-element").shadow().find(".my-part").should("exist");
});
```

For more examples and basic usages see ´cypress/component´ examples

> **Note**: You may prefer use `includeShadowDom` option to avoid write `shadow()` command on every test.
>
> ```typescript
> export default defineConfig({
> includeShadowDom: true,
> component: {
> devServer: {
> framework: "cypress-ct-html",
> // more config here
> },
> },
> });
> ```
## Credits
Much of the code and inspiration for this package comes from the work of [redfox-mx](https://github.com/redfox-mx) and his [cypress-lit](https://github.com/redfox-mx/cypress-lit) repository. A big thank you for his contributions to the community.
Binary file added bun.lockb
Binary file not shown.
11 changes: 11 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from "cypress";

export default defineConfig({
component: {
devServer: {
framework: "cypress-ct-webcomponents" as any,
bundler: "vite",
viteConfig: {},
},
},
});
55 changes: 55 additions & 0 deletions cypress/component/elements/counter-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class CounterComponent extends HTMLElement {
private count: number;
private shadow: ShadowRoot;

constructor() {
super();
this.count = 0;
this.shadow = this.attachShadow({ mode: "open" });

this.shadow.innerHTML = `
<div class="counter">
<span id="count">Count is ${this.count}</span>
<button id="increment">+</button>
</div>
`;

this.shadow.querySelector("#increment")?.addEventListener("click", this.increment.bind(this));
}

static get observedAttributes() {
return ["value"];
}

attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
if (name === "value") {
this.value = Number(newValue);
}
}

get value() {
return this.count;
}

set value(newValue: number) {
this.count = newValue;
this.updateCount();
}

increment() {
this.value++;
}

decrement() {
this.value--;
}

updateCount() {
const countElement = this.shadow.querySelector("#count");
if (countElement) {
countElement.textContent = `Count is ${this.count}`;
}
}
}

customElements.define("counter-component", CounterComponent);
61 changes: 61 additions & 0 deletions cypress/component/mount.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import "./elements/counter-component";

describe("mount", () => {
it("should mount element", () => {
cy.mount(document.createElement("counter-component"));
cy.get("counter-component").should("exist");
cy.get("counter-component").shadow().contains("Count is 0");
});

it("should return chainable reference to element", () => {
const counter = cy.mount<"counter-component">(document.createElement("counter-component"));
counter.shadow().contains("Count is 0");
});

it("should clean DOM if mount is called more than one time", () => {
const component1 = document.createElement("div");
component1.id = "mount1";

const component2 = document.createElement("div");
component2.id = "mount2";

cy.mount(component1);
cy.mount(component2);

cy.get("#mount1").should("not.exist");
cy.get("#mount2").should("exist");
});
});

describe("<counter-component .../>", () => {
it("should mount", () => {
cy.mount(document.createElement("counter-component"));
cy.get("counter-component").should("exist");
});

it("should set attribute value", () => {
const component = document.createElement("counter-component");
component.setAttribute("value", "5");

cy.mount(component);
cy.get("counter-component").shadow().contains("Count is 5");

cy.get("counter-component").should("have.prop", "value", 5);
});

it("should increment counter", () => {
cy.mount(document.createElement("counter-component"));
cy.get("counter-component").shadow().contains("+").click();

cy.get("counter-component").shadow().contains("Count is 1");
});

it("should track value of counter", () => {
cy.mount(document.createElement("counter-component"));
cy.get("counter-component").should("have.prop", "value", 0);

cy.get("counter-component").shadow().contains("+").click();

cy.get("counter-component").should("have.prop", "value", 1);
});
});
1 change: 1 addition & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="cypress" />
12 changes: 12 additions & 0 deletions cypress/support/component-index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>
3 changes: 3 additions & 0 deletions cypress/support/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { mount } from '../../src/index';

Cypress.Commands.add('mount', mount)
Loading

0 comments on commit 111fe70

Please sign in to comment.