Skip to content

Commit

Permalink
Merge branch 'improve-test-harness' into feat/int-selfie
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg committed Dec 17, 2023
2 parents a1d9a52 + 0bf908c commit fc5cc07
Show file tree
Hide file tree
Showing 78 changed files with 10,782 additions and 139 deletions.
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* text=auto eol=lf
*.jar binary
*.png binary
*.png binary
*.webp binary
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install JDK ${{ matrix.jre }}
uses: actions/setup-java@v3
with:
Expand All @@ -33,7 +33,7 @@ jobs:
- name: gradlew build
run: ./gradlew build
- name: junit result
uses: mikepenz/action-junit-report@v3
uses: mikepenz/action-junit-report@v4
if: always() # always run even if the previous step fails
with:
check_name: JUnit ${{ matrix.jre }} ${{ matrix.os }}
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/publish-kdoc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
on: workflow_dispatch

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish to Cloudflare Pages
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install JDK 17
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: 17
- name: Gradle caching
uses: gradle/gradle-build-action@v2
with:
gradle-home-cache-cleanup: true
- name: Build
run: ./gradlew :dokkatooGeneratePublicationHtml
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: 0a95e814ccf2b6a95d2dc3bea0a4a2b4
projectName: selfie-kdoc
directory: build/dokka/html
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.idea/
.gradle/
build/
bin/
.DS_Store
97 changes: 5 additions & 92 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,98 +1,11 @@
# Selfie
# <img align="left" src="docs/selfie_logo_only.png"> Selfie

- Precise snapshot testing for the JVM and Javascript (others TBD)
- Supports text and binary data.
- Friendly to humans and version control.
- In-place [literal snapshots](#literal-snapshots).
- Allows [lenses](#lenses) to verify multiple aspects of an object under test.
- e.g. pretty-printed JSON, or only the rendered plaintext of an HTML document, etc.
Snapshot testing is the fastest and most precise mechanism to record and specify the behavior of your system and its components.

ere is a very simple test which snapshots the HTML served at various URLs.
Robots are writing their own code. Are you still writing assertions by hand?

```java
@Test public void gzipFavicon() {
expectSelfie(get("/favicon.ico", ContentEncoding.GZIP)).toMatchDisk();
}
@Test public void orderFlow() {
expectSelfie(get("/orders")).toMatchDisk("initial");
postOrder();
expectSelfie(get("/orders")).toMatchDisk("ordered");
}
```

This will generate a snapshot file like so:

```
╔═ gzipFavicon ═╗ base64 length 823 bytes
H4sIAAAAAAAA/8pIzcnJVyjPL8pJUQQAlQYXAAAA
╔═ orderFlow/initial ═╗
<html><body>
<button>Submit order</button>
</body></html>
╔═ orderFlow/ordered ═╗
<html><body>
<p>Thanks for your business!</p>
<details>
<summary>Order information</summary>
<p>Tracking #ABC123</p>
</details>
</body></html>
```

### Literal snapshots

A great thing about snapshots is that they are fast to write and update. A downside is that the asserted value is opaque. But it doesn't have to be! Just swap `toMatchDisk` for `toBe`.

```java
@Test public void preventCssBloat() {
// selfie can update this literal value for you ▼
int size = expectSelfie(get("/index.css").length).toBe(5_236);
if (size > 100_000) {
Assert.fail("CSS has gotten too big!");
}
}
```

Now we can see at a glance how a PR has affected the bundled size of our CSS. We can easily automate manual processes and turn "test execution time" values into source code constants, without wasting programmer time on fragile manual workflows.

### Lenses

When snapshotting HTML, you might want to look at only the rendered text, ignoring tags, classes, and all that.

```java
public SelfieConfig extends com.diffplug.selfie.SelfieConfig {
@Override public @Nullable Snapshot intercept(Class<?> className, String testName, Snapshot snapshot) {
if (!snapshot.getValue().isBinary()) {
String content = snapshot.getValue().valueString();
if (content.contains("<html>")) {
return snapshot.lens("plaintext", Jsoup.parse(content).text());
}
}
return null;
}
}
```

Now we'll get a snapshot file like so:

```
╔═ orderFlow/ordered ═╗
<html><body>
<p>Thanks for your business!</p>
<details>
<summary>Order information</summary>
<p>Tracking #ABC123</p>
</details>
</body></html>
╔═ orderFlow/ordered[plaintext] ═╗
Thanks for your business!
Order information
Tracking #ABC123
```

Lenses can make PRs easier to review, by putting the focus on whichever aspect of the snapshot is relevant to the change.
More info for [jvm](https://selfie.diffplug.com/jvm), [js](https://selfie.diffplug.com/js), and [other platforms](https://selfie.diffplug.com/other-platforms).

### Acknowledgements

- Heavily inspired by [origin-energy's java-snapshot-testing](https://github.com/origin-energy/java-snapshot-testing).
- Which in turn is heavily inspired by [Facebook's jest-snapshot](https://jestjs.io/docs/snapshot-testing).
Heavily inspired by [origin-energy's java-snapshot-testing](https://github.com/origin-energy/java-snapshot-testing), which in turn is heavily inspired by [Facebook's jest-snapshot](https://jestjs.io/docs/snapshot-testing).
28 changes: 28 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,31 @@ repositories {
mavenCentral()
}
apply from: rootProject.file('gradle/spotless.gradle')

// ./gradlew :dokkatooGeneratePublicationHtml
// https://github.com/Kotlin/dokka/issues/3131
// if IntelliJ is running, then you'll also get a localhost server link
def HAS_DOCS = [
'selfie-lib',
'selfie-runner-junit5'
]
subprojects {
if (name in HAS_DOCS) {
apply from: rootProject.file("gradle/dokka/dokkatoo.gradle")
}
}
apply from: rootProject.file("gradle/dokka/dokkatoo.gradle")
dependencies {
// aggregate both subproject-hello and subproject-world
// the subprojects must also have Dokkatoo applied
for (p in HAS_DOCS) {
dokkatoo(project(":${p}"))
}

// This is required at the moment, see https://github.com/adamko-dev/dokkatoo/issues/14
dokkatooPluginHtml(
dokkatoo.versions.jetbrainsDokka.map { dokkaVersion ->
"org.jetbrains.dokka:all-modules-page-plugin:$dokkaVersion"
}
)
}
32 changes: 32 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel
1 change: 1 addition & 0 deletions docs/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.9.0
5 changes: 5 additions & 0 deletions docs/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
29 changes: 29 additions & 0 deletions docs/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import nextMDX from "@next/mdx";
import withSearch from "./src/mdx/search.mjs";
import { remarkPlugins } from "./src/mdx/remark.mjs";
import { rehypePlugins } from "./src/mdx/rehype.mjs";
import { recmaPlugins } from "./src/mdx/recma.mjs";

const withMDX = nextMDX({
options: {
remarkPlugins,
rehypePlugins,
recmaPlugins,
providerImportSource: "@mdx-js/react",
},
});

/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
images: {
unoptimized: true,
},
reactStrictMode: true,
pageExtensions: ["js", "jsx", "ts", "tsx", "mdx"],
experimental: {
scrollRestoration: true,
},
};

export default withSearch(withMDX(nextConfig));
Loading

0 comments on commit fc5cc07

Please sign in to comment.