Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests #1

Merged
merged 4 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Set update schedule for GitHub Actions

version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "daily"

- package-ecosystem: "npm"
directories:
- "/"
- "integration-tests/"
groups:
# Update all npm dependencies together
# https://github.blog/2023-08-24-a-faster-way-to-manage-version-updates-with-dependabot/
"NPM dependencies":
"patterns":
- "*"

schedule:
interval: "daily"
57 changes: 57 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: Run tests
on:
pull_request: ~
push:
branches:
- main

jobs:
build:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
neovim_version: ["nightly", "stable"]

steps:
- uses: actions/checkout@v4.2.2
- name: Set up dependencies
run: |
# ripgrep is (of course) a dependency
which rg || {
sudo apt-get install ripgrep
}

- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ inputs.nvim_version }}

- uses: pnpm/action-setup@v4.0.0
- uses: actions/setup-node@v4.1.0
with:
node-version-file: .nvmrc
cache: "pnpm"
- run: pnpm install

- name: Install dependencies
uses: cypress-io/github-action@v6
with:
runTests: false
# https://github.com/cypress-io/github-action/blob/bff0ae58f02476fbc4b13229bcaac2767f645b3e/README.md#pnpm-workspaces

- name: Cypress test
uses: cypress-io/github-action@v6
with:
install: false
command: pnpm cy:run

- uses: actions/upload-artifact@v4.4.3
# add the line below to store screenshots only on failures
# if: failure()
if: failure()
with:
name: cypress-screenshots
path: integration-tests/cypress/screenshots
if-no-files-found: ignore # 'warn' or 'error' are also available, defaults to `warn`
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
.nvim/
node_modules
integration-tests/test-environment/.repro
integration-tests/test-environment/testdirs/
integration-tests/cypress/screenshots/
integration-tests/dist
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
registry = "https://registry.npmjs.org"
save-exact=true
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
21.7.3
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
integration-tests/dist/
pnpm-lock.yaml
6 changes: 6 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
proseWrap: always
semi: false
plugins:
- prettier-plugin-organize-imports
- prettier-plugin-packagejson
3 changes: 3 additions & 0 deletions .stylua.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
indent_width = 2
indent_type = "Spaces"
column_width = 80
52 changes: 52 additions & 0 deletions integration-tests/MyTestDirectory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Note: This file is autogenerated. Do not edit it directly.
//
// Describes the contents of the test directory, which is a blueprint for
// files and directories. Tests can create a unique, safe environment for
// interacting with the contents of such a directory.
//
// Having strong typing for the test directory contents ensures that tests can
// be written with confidence that the files and directories they expect are
// actually found. Otherwise the tests are brittle and can break easily.

import { z } from "zod"

export const MyTestDirectorySchema = z.object({
name: z.literal("test-environment"),
type: z.literal("directory"),
contents: z.object({
"initial-file.txt": z.object({
name: z.literal("initial-file.txt"),
type: z.literal("file"),
extension: z.literal("txt"),
stem: z.literal("initial-file."),
}),
"other-file.txt": z.object({
name: z.literal("other-file.txt"),
type: z.literal("file"),
extension: z.literal("txt"),
stem: z.literal("other-file."),
}),
"test-setup.lua": z.object({
name: z.literal("test-setup.lua"),
type: z.literal("file"),
extension: z.literal("lua"),
stem: z.literal("test-setup."),
}),
}),
})

export const MyTestDirectoryContentsSchema =
MyTestDirectorySchema.shape.contents
export type MyTestDirectoryContentsSchemaType = z.infer<
typeof MyTestDirectorySchema
>

export type MyTestDirectory = MyTestDirectoryContentsSchemaType["contents"]

export const testDirectoryFiles = z.enum([
"initial-file.txt",
"other-file.txt",
"test-setup.lua",
".",
])
export type MyTestDirectoryFile = z.infer<typeof testDirectoryFiles>
25 changes: 25 additions & 0 deletions integration-tests/client/__global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This defines a way for the test runner to start Neovim. We need a way for
// the test runner to do this because it doesn't have direct access to either
// the backend or the frontend

import type { TestDirectory } from "@tui-sandbox/library/src/server/types.ts"
import type { MyTestDirectory, testDirectoryFiles } from "../MyTestDirectory"
import type { MyStartNeovimServerArguments } from "./neovim-client.ts"

export type NeovimContext = {
contents: MyTestDirectory
/** provides easy access to all relative file paths from the root of the test
* directory */
files: (typeof testDirectoryFiles)["enum"]
testDirectory: TestDirectory
}

declare global {
interface Window {
startNeovim(
startArguments?: MyStartNeovimServerArguments,
): Promise<NeovimContext>
}
}

export {}
46 changes: 46 additions & 0 deletions integration-tests/client/neovim-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NeovimClient } from "@tui-sandbox/library/src/client"
import { z } from "zod"
import {
MyTestDirectoryContentsSchema,
testDirectoryFiles,
} from "../MyTestDirectory"
import type { NeovimContext } from "./__global"

const app = document.querySelector<HTMLElement>("#app")
if (!app) {
throw new Error("No app element found")
}

const client = new NeovimClient(app)

/** The arguments given from the tests to send to the server. This one contains
* the test environment's files in a type safe manner, whereas the server only
* requires strings. */
export const myStartNeovimArguments = z.object({
filename: z
.union([
testDirectoryFiles,
z.object({
openInVerticalSplits: z.array(testDirectoryFiles),
}),
])
.optional(),
})
export type MyStartNeovimServerArguments = z.infer<
typeof myStartNeovimArguments
>

/** Entrypoint for the test runner (cypress) */
window.startNeovim = async function (
startArgs?: MyStartNeovimServerArguments,
): Promise<NeovimContext> {
const neovim = await client.startNeovim({
filename: startArgs?.filename ?? "initial-file.txt",
})

return {
contents: MyTestDirectoryContentsSchema.parse(neovim.contents),
files: testDirectoryFiles.enum,
testDirectory: neovim,
}
}
34 changes: 34 additions & 0 deletions integration-tests/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { defineConfig } from "cypress"
import { mkdir, readdir, rm } from "fs/promises"
import path from "path"
import { fileURLToPath } from "url"

const __dirname = fileURLToPath(new URL(".", import.meta.resolve(".")))

const testEnvironmentDir = path.join(__dirname, "test-environment")
const testdirs = path.join(testEnvironmentDir, "testdirs")

export default defineConfig({
e2e: {
setupNodeEvents(on, _config) {
on("after:browser:launch", async (): Promise<void> => {
// delete everything under the ./test-environment/testdirs/ directory
await mkdir(testdirs, { recursive: true })
const files = await readdir(testdirs)

console.log("Cleaning up testdirs directory...")

for (const file of files) {
const testdir = path.join(testdirs, file)
console.log(`Removing ${testdir}`)
await rm(testdir, { recursive: true })
}
})
},
experimentalRunAllSpecs: true,
retries: {
runMode: 2,
openMode: 0,
},
},
})
20 changes: 20 additions & 0 deletions integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
describe("the basics", () => {
it("shows words in other files as suggestions", () => {
cy.visit("http://localhost:5173")
cy.startNeovim().then(() => {
// wait until text on the start screen is visible
cy.contains("If you see this text, Neovim is ready!")
cy.typeIntoTerminal(
// clear the current line and enter insert mode
"cc",
)

// this will match "Hippopotamus123" from ../../../test-environment/other-file.txt
//
// If the plugin works, this text should show up as a suggestion.
cy.typeIntoTerminal("hip")

cy.contains("Hippopotamus123")
})
})
})
12 changes: 12 additions & 0 deletions integration-tests/cypress/e2e/cmp-rg/health_spec.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
describe("the healthcheck", () => {
it("does not show any errors when ripgrep is installed", () => {
cy.visit("http://localhost:5173")
cy.startNeovim().then(() => {
// wait until text on the start screen is visible
cy.contains("If you see this text, Neovim is ready!")

cy.typeIntoTerminal(":checkhealth blink-cmp-rg{enter}")
cy.contains("OK blink-cmp-rg")
})
})
})
36 changes: 36 additions & 0 deletions integration-tests/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-namespace */
/// <reference types="cypress" />

import "../../client/__global.ts"
import type { NeovimContext } from "../../client/__global.ts"
import type { MyStartNeovimServerArguments } from "../../client/neovim-client.ts"

Cypress.Commands.add(
"startNeovim",
(startArguments?: MyStartNeovimServerArguments) => {
cy.window().then((win) => {
return win.startNeovim(startArguments)
})
},
)

Cypress.Commands.add(
"typeIntoTerminal",
(text: string, options?: Partial<Cypress.TypeOptions>) => {
// the syntax for keys is described here:
// https://docs.cypress.io/api/commands/type
cy.get("textarea").focus().type(text, options)
},
)

declare global {
namespace Cypress {
interface Chainable {
startNeovim(args?: MyStartNeovimServerArguments): Chainable<NeovimContext>
typeIntoTerminal(
text: string,
options?: Partial<Cypress.TypeOptions>,
): Chainable<void>
}
}
}
17 changes: 17 additions & 0 deletions integration-tests/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import "./commands.ts"
Loading