Convert tests to Vitest (#4)

This commit is contained in:
Darius
2022-01-11 00:33:13 -06:00
committed by GitHub
parent 661a253d8c
commit 5b2db4dd69
72 changed files with 544 additions and 1984 deletions

9
packages/website/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
node_modules
/.cache
/build
/public/build
.env
/public/api
cypress/videos
cypress/screenshots

View File

@@ -0,0 +1,4 @@
import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";
hydrate(<RemixBrowser />, document);

View File

@@ -0,0 +1,21 @@
import { renderToString } from "react-dom/server";
import { RemixServer } from "remix";
import type { EntryContext } from "remix";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders
});
}

View File

@@ -0,0 +1,3 @@
export function raise(error: unknown): never {
throw error instanceof Error ? error : new Error(String(error))
}

View File

@@ -0,0 +1,19 @@
{/* prettier-ignore */}
```tsx
import * as React from "react"
import { Embed, Button } from "reacord"
function Counter() {
const [count, setCount] = React.useState(0)
return (
<>
<Embed title="Counter">
This button has been clicked {count} times.
</Embed>
<Button onClick={() => setCount(count + 1)}>
+1
</Button>
</>
)
}
```

View File

@@ -0,0 +1,31 @@
import type { ComponentPropsWithoutRef } from "react"
import { Link } from "remix"
export type AppLinkProps = ComponentPropsWithoutRef<"a"> & {
type: "internal" | "external" | "router"
to: string
}
export function AppLink({ type, to, children, ...props }: AppLinkProps) {
if (type === "internal") {
return (
<a href={to} {...props}>
{children}
</a>
)
}
if (type === "external") {
return (
<a href={to} target="_blank" rel="noopener noreferrer" {...props}>
{children}
</a>
)
}
return (
<Link to={to} {...props}>
{children}
</Link>
)
}

View File

@@ -0,0 +1,10 @@
import { createContext, useContext } from "react"
import type { GuideLink } from "~/modules/navigation/load-guide-links.server"
const Context = createContext<GuideLink[]>([])
export const GuideLinksProvider = Context.Provider
export function useGuideLinksContext() {
return useContext(Context)
}

View File

@@ -0,0 +1,40 @@
import glob from "fast-glob"
import grayMatter from "gray-matter"
import { readFile } from "node:fs/promises"
import { join, parse } from "node:path"
import type { AppLinkProps } from "~/modules/navigation/app-link"
const guidesFolder = "app/routes/guides"
export type GuideLink = {
title: string
order: number
link: AppLinkProps
}
export async function loadGuideLinks(): Promise<GuideLink[]> {
const guideFiles = await glob(`**/*.md`, { cwd: guidesFolder })
const links: GuideLink[] = await Promise.all(
guideFiles.map(async (file) => {
const { data } = grayMatter(await readFile(join(guidesFolder, file)))
let order = data.order
if (!Number.isFinite(order)) {
order = Number.POSITIVE_INFINITY
}
return {
title: data.meta?.title,
order,
link: {
type: "router",
to: `/guides/${parse(file).name}`,
children: data.meta?.title,
},
}
}),
)
return links.sort((a, b) => a.order - b.order)
}

View File

@@ -0,0 +1,37 @@
import {
CodeIcon,
DocumentTextIcon,
ExternalLinkIcon,
} from "@heroicons/react/solid"
import type { AppLinkProps } from "~/modules/navigation/app-link"
import { inlineIconClass } from "../ui/components"
export const mainLinks: AppLinkProps[] = [
{
type: "internal",
to: "/guides/getting-started",
children: (
<>
<DocumentTextIcon className={inlineIconClass} /> Guides
</>
),
},
{
type: "internal",
to: "/api",
children: (
<>
<CodeIcon className={inlineIconClass} /> API Reference
</>
),
},
{
type: "external",
to: "https://github.com/itsMapleLeaf/reacord",
children: (
<>
<ExternalLinkIcon className={inlineIconClass} /> GitHub
</>
),
},
]

View File

@@ -0,0 +1,42 @@
import { useGuideLinksContext } from "~/modules/navigation/guide-links-context"
import { linkClass } from "../ui/components"
import { PopoverMenu } from "../ui/popover-menu"
import { AppLink } from "./app-link"
import { mainLinks } from "./main-links"
export function MainNavigation() {
const guideLinks = useGuideLinksContext()
return (
<nav className="flex justify-between items-center h-16">
<a href="/">
<h1 className="text-3xl font-light">reacord</h1>
</a>
<div className="hidden md:flex gap-4">
{mainLinks.map((link) => (
<AppLink {...link} key={link.to} className={linkClass} />
))}
</div>
<div className="md:hidden">
<PopoverMenu>
{mainLinks.map((link) => (
<AppLink
{...link}
role="menuitem"
key={link.to}
className={PopoverMenu.itemClass}
/>
))}
<hr className="border-0 h-[2px] bg-black/50" />
{guideLinks.map(({ link }) => (
<AppLink
{...link}
role="menuitem"
key={link.to}
className={PopoverMenu.itemClass}
/>
))}
</PopoverMenu>
</div>
</nav>
)
}

View File

@@ -0,0 +1,24 @@
import clsx from "clsx"
export const maxWidthContainer = clsx`mx-auto w-full max-w-screen-lg px-4`
export const inlineIconClass = clsx`inline w-5 align-sub`
export const linkClass = clsx`
font-medium inline-block relative
opacity-60 hover:opacity-100 transition-opacity
after:absolute after:block after:w-full after:h-px after:bg-white/50 after:translate-y-[3px] after:opacity-0 after:transition
hover:after:translate-y-[-1px] hover:after:opacity-100
`
export const docsProseClass = clsx`
prose prose-invert
prose-h1:font-light prose-h1:mb-4 prose-h1:text-3xl lg:prose-h1:text-4xl
prose-h2:font-light
prose-h3:font-light
prose-p:my-4
prose-a:font-medium prose-a:text-emerald-400 hover:prose-a:no-underline
prose-strong:font-medium prose-strong:text-emerald-400
prose-pre:font-monospace prose-pre:overflow-x-auto
max-w-none
`

View File

@@ -0,0 +1,32 @@
import { MenuAlt4Icon } from "@heroicons/react/outline"
import clsx from "clsx"
import React from "react"
import { linkClass } from "./components"
export function PopoverMenu({ children }: { children: React.ReactNode }) {
return (
<div className="relative" x-data="{ open: false }">
<button title="Menu" className={linkClass} x-on:click="open = !open">
<MenuAlt4Icon className="w-6" />
</button>
<div
role="menu"
className={`
w-48 max-h-[calc(100vh-4rem)]
absolute right-0 top-[calc(100%+8px)]
bg-slate-800 shadow rounded-lg
overflow-hidden overflow-y-auto
transition-all
`}
x-bind:class="open ? 'visible opacity-100' : 'invisible opacity-0 translate-y-3'"
>
{children}
</div>
</div>
)
}
PopoverMenu.itemClass = clsx`
px-3 py-2 transition text-left font-medium block
opacity-50 hover:opacity-100 hover:bg-black/30
`

View File

@@ -0,0 +1,133 @@
/**
* Nord Theme Originally by Arctic Ice Studio
* https://nordtheme.com
*
* Ported for PrismJS by Zane Hitchcoxc (@zwhitchcox) and Gabriel Ramos (@gabrieluizramos)
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
/* font-family: "Fira Code", Consolas, Monaco, "Andale Mono", "Ubuntu Mono",
monospace; */
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.7;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
/* background: #2e3440; */
background: rgba(0, 0, 0, 0.3);
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #636f88;
}
.token.punctuation {
color: #81a1c1;
}
.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #81a1c1;
}
.token.number {
color: #b48ead;
}
.token.boolean {
color: #81a1c1;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a3be8c;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #81a1c1;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #88c0d0;
}
.token.keyword {
color: #81a1c1;
}
.token.regex,
.token.important {
color: #ebcb8b;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.code-line.highlight-line {
background-color: rgba(255, 255, 255, 0.08);
padding: 0 1rem;
margin: 0 -1rem;
display: block;
}

View File

@@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.prose aside {
@apply opacity-75 italic border-l-4 pl-3 border-white/50;
}
}

4
packages/website/app/react.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
import "react"
declare module "react" {
export function createContext<Value>(): Context<Value | undefined>
}

2
packages/website/app/remix.env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" />

View File

@@ -0,0 +1,68 @@
import packageJson from "reacord/package.json"
import type { LinksFunction, LoaderFunction, MetaFunction } from "remix"
import {
Links,
LiveReload,
Meta,
Outlet,
ScrollRestoration,
useLoaderData,
} from "remix"
import { GuideLinksProvider } from "~/modules/navigation/guide-links-context"
import type { GuideLink } from "~/modules/navigation/load-guide-links.server"
import { loadGuideLinks } from "~/modules/navigation/load-guide-links.server"
import prismThemeCss from "~/modules/ui/prism-theme.css"
export const meta: MetaFunction = () => ({
title: "Reacord",
description: packageJson.description,
})
export const links: LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "preload",
as: "style",
href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap",
},
{ rel: "stylesheet", href: "/tailwind.css" },
{ rel: "stylesheet", href: prismThemeCss },
]
type LoaderData = {
guideLinks: GuideLink[]
}
export const loader: LoaderFunction = async () => {
const data: LoaderData = {
guideLinks: await loadGuideLinks(),
}
return data
}
export default function App() {
const data: LoaderData = useLoaderData()
return (
<html lang="en" className="bg-slate-900 text-slate-100">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
<script defer src="https://unpkg.com/alpinejs@3.7.1/dist/cdn.min.js" />
</head>
<body>
<GuideLinksProvider value={data.guideLinks}>
<Outlet />
</GuideLinksProvider>
<ScrollRestoration />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
)
}

View File

@@ -0,0 +1,38 @@
import clsx from "clsx"
import { Outlet } from "remix"
import { AppLink } from "~/modules/navigation/app-link"
import { useGuideLinksContext } from "~/modules/navigation/guide-links-context"
import { MainNavigation } from "~/modules/navigation/main-navigation"
import {
docsProseClass,
linkClass,
maxWidthContainer,
} from "~/modules/ui/components"
export default function GuidePage() {
const guideLinks = useGuideLinksContext()
return (
<>
<header className="bg-slate-700/30 shadow sticky top-0 backdrop-blur-sm transition z-10 flex">
<div className={maxWidthContainer}>
<MainNavigation />
</div>
</header>
<main className={clsx(maxWidthContainer, "mt-8 flex items-start gap-4")}>
<nav className="w-48 sticky top-24 hidden md:block">
<h2 className="text-2xl">Guides</h2>
<ul className="mt-3 flex flex-col gap-2 items-start">
{guideLinks.map(({ link }) => (
<li key={link.to}>
<AppLink {...link} className={linkClass} />
</li>
))}
</ul>
</nav>
<section className={clsx(docsProseClass, "pb-8 flex-1 min-w-0")}>
<Outlet />
</section>
</main>
</>
)
}

View File

@@ -0,0 +1,47 @@
---
order: 3
meta:
title: Buttons
description: Using button components
---
# Buttons
Use the `<Button />` component to create a message with a button, and use the `onClick` callback to respond to button clicks.
```jsx
import { Button } from "reacord"
function Counter() {
const [count, setCount] = useState(0)
return (
<>
You've clicked the button {count} times.
<Button label="+1" onClick={() => setCount(count + 1)} />
</>
)
}
```
The `onClick` callback receives an `event` object. It includes some information, such as the user who clicked the button, and functions for creating new replies in response. These functions return message instances.
```jsx
import { Button } from "reacord"
function TheButton() {
function handleClick(event) {
const name = event.guild.member.displayName || event.user.username
const publicReply = event.reply(`${name} clicked the button. wow`)
setTimeout(() => publicReply.destroy(), 3000)
const privateReply = event.ephemeralReply("good job, you clicked it")
privateReply.deactivate() // we don't need to listen to updates on this
}
return <Button label="click me i dare you" onClick={handleClick} />
}
```
See the [API reference](/api/index.html#ButtonProps) for more information.

View File

@@ -0,0 +1,11 @@
---
meta:
title: Using Reacord with other libraries
description: Adapting Reacord to another Discord library
---
# Using Reacord with other libraries
Reacord's core is built to be library agnostic, and can be adapted to libraries other than Discord.js. However, Discord.js is the only built-in adapter at the moment, and the adapter API is still a work in progress.
If you're interested in creating a custom adapter, [see the code for the Discord.js adapter as an example](https://github.com/itsMapleLeaf/reacord/blob/main/packages/reacord/library/core/reacord-discord-js.ts). Feel free to [create an issue on GitHub](https://github.com/itsMapleLeaf/reacord/issues/new) if you run into issues.

View File

@@ -0,0 +1,64 @@
---
order: 2
meta:
title: Embeds
description: Using embed components
---
# Embeds
Reacord comes with an `<Embed />` component for sending rich embeds.
```jsx
import { Embed } from "reacord"
function FancyMessage({ title, description }) {
return (
<Embed
title={title}
description={description}
color={0x00ff00}
timestamp={Date.now()}
/>
)
}
```
```jsx
reacord.send(channelId, <FancyMessage title="Hello" description="World" />)
```
Reacord also comes with multiple embed components, for defining embeds on a piece-by-piece basis. This enables composition:
```jsx
import { Embed, EmbedTitle } from "reacord"
function FancyDetails({ title, description }) {
return (
<>
<EmbedTitle>{title}</EmbedTitle>
{/* embed descriptions are just text */}
{description}
</>
)
}
function FancyMessage({ children }) {
return (
<Embed color={0x00ff00} timestamp={Date.now()}>
{children}
</Embed>
)
}
```
```jsx
reacord.send(
channelId,
<FancyMessage>
<FancyDetails title="Hello" description="World" />
</FancyMessage>,
)
```
See the [API Reference](/api/index.html#EmbedAuthorProps) for the full list of embed components.

View File

@@ -0,0 +1,44 @@
---
order: 0
meta:
title: Getting Started
description: Learn how to get started with Reacord.
---
# Getting Started
This guide assumes some familiarity with JavaScript, [React](https://reactjs.org), [Discord.js](https://discord.js.org) and the [Discord API](https://discord.dev). Keep these pages as reference if you need it.
**Note:** Ensure your project has support for running code with JSX. I recommend using [esno](https://npm.im/esno).
## Install
```bash
# npm
npm install reacord react discord.js
# yarn
yarn add reacord react discord.js
# pnpm
pnpm add reacord react discord.js
```
## Setup
Create a Discord.js client and a Reacord instance:
```js
// main.js
import { Client } from "discord.js"
import { ReacordDiscordJs } from "reacord"
const client = new Client()
const reacord = new ReacordDiscordJs(client)
client.on("ready", () => {
console.log("Ready!")
})
await client.login(process.env.BOT_TOKEN)
```

View File

@@ -0,0 +1,25 @@
---
order: 3
meta:
title: Links
description: Using link components
---
# Links
In Discord, links are a type of button, and they work similarly. Clicking on it leads you to the given URL. They only have one style, and can't be listened to for clicks.
```jsx
import { Link } from "reacord"
function AwesomeLinks() {
return (
<>
<Link label="look at this" url="https://google.com" />
<Link label="wow" url="https://youtube.com/watch?v=dQw4w9WgXcQ" />
</>
)
}
```
See the [API reference](/api/index.html#Link) for more information.

View File

@@ -0,0 +1,71 @@
---
order: 4
meta:
title: Select Menus
description: Using select menu components
---
# Select Menus
To create a select menu, use the `Select` component, and pass a list of `Option` components as children. Use the `value` prop to set a currently selected value. You can respond to changes in the value via `onChangeValue`.
```jsx
export function FruitSelect({ onConfirm }) {
const [value, setValue] = useState()
return (
<>
<Select
placeholder="choose a fruit"
value={value}
onChangeValue={setValue}
>
<Option value="🍎" />
<Option value="🍌" />
<Option value="🍒" />
</Select>
<Button
label="confirm"
disabled={value == undefined}
onClick={() => {
if (value) onConfirm(value)
}}
/>
</>
)
}
```
```jsx
const instance = reacord.send(
channelId,
<FruitSelect
onConfirm={(value) => {
instance.render(`you chose ${value}`)
instance.deactivate()
}}
/>,
)
```
For a multi-select, use the `multiple` prop, then you can use `values` and `onChangeMultiple` to handle multiple values.
```tsx
export function FruitSelect({ onConfirm }) {
const [values, setValues] = useState([])
return (
<Select
placeholder="choose a fruit"
values={values}
onChangeMultiple={setValues}
>
<Option value="🍎" />
<Option value="🍌" />
<Option value="🍒" />
</Select>
)
}
```
The Select component accepts a variety of props beyond what's shown here. See the [API reference](/api/index.html#SelectChangeEvent) for more information.

View File

@@ -0,0 +1,167 @@
---
order: 1
meta:
title: Sending Messages
description: Sending messages by creating Reacord instances
---
# Sending Messages with Instances
You can send messages via Reacord to a channel like so.
```jsx
const channelId = "abc123deadbeef"
client.on("ready", () => {
reacord.send(channelId, "Hello, world!")
})
```
The `.send()` function creates a **Reacord instance**. You can pass strings, numbers, or anything that can be rendered by React, such as JSX!
Components rendered through this instance can include state and effects, and the message on Discord will update automatically.
```jsx
function Uptime() {
const [startTime] = useState(Date.now())
const [currentTime, setCurrentTime] = useState(Date.now())
useEffect(() => {
const interval = setInterval(() => {
currentTime(Date.now())
}, 3000)
return () => clearInterval(interval)
}, [])
return <>this message has been shown for {currentTime - startTime}ms</>
}
client.on("ready", () => {
reacord.send(channelId, <Uptime />)
})
```
The instance can be rendered to multiple times, which will update the message each time.
```jsx
const Hello = ({ subject }) => <>Hello, {subject}!</>
client.on("ready", () => {
const instance = reacord.send(channel)
instance.render(<Hello subject="World" />)
instance.render(<Hello subject="Moon" />)
})
```
## Cleaning Up Instances
If you no longer want to use the instance, you can clean it up in a few ways:
- `instance.destroy()` - This will remove the message.
- `instance.deactivate()` - This will keep the message, but it will disable the components on the message, and no longer listen to user interactions.
By default, Reacord has a max limit on the number of active instances, and deactivates older instances to conserve memory. This can be configured through the Reacord options:
```js
const reacord = new ReacordDiscordJs(client, {
// after sending four messages,
// the first one will be deactivated
maxInstances: 3,
})
```
## Discord Slash Commands
<aside>
This section also applies to other kinds of application commands, such as context menu commands.
</aside>
To reply to a command interaction, use the `.reply()` function. This function returns an instance that works the same way as the one from `.send()`. Here's an example:
```jsx
import { Client } from "discord.js"
import * as React from "react"
import { Button, ReacordDiscordJs } from "reacord"
const client = new Client({ intents: [] })
const reacord = new ReacordDiscordJs(client)
client.on("ready", () => {
client.application?.commands.create({
name: "ping",
description: "pong!",
})
})
client.on("interactionCreate", (interaction) => {
if (interaction.isCommand() && interaction.commandName === "ping") {
// Use the reply() function instead of send
reacord.reply(interaction, <>pong!</>)
}
})
client.login(process.env.DISCORD_TOKEN)
```
<aside>
This example uses <a href="https://discord.com/developers/docs/interactions/application-commands#registering-a-command">global commands</a>, so the command might take a while to show up 😅
</aside>
However, the process of creating commands can get really repetitive and error-prone. A command framework could help with this, or you could make a small helper:
```jsx
function handleCommands(client, commands) {
client.on("ready", () => {
for (const { name, description } of commands) {
client.application?.commands.create({ name, description })
}
})
client.on("interactionCreate", (interaction) => {
if (interaction.isCommand()) {
for (const command of commands) {
if (interaction.commandName === command.name) {
command.run(interaction)
}
}
}
})
}
```
```jsx
handleCommands(client, [
{
name: "ping",
description: "pong!",
run: (interaction) => {
reacord.reply(interaction, <>pong!</>)
},
},
{
name: "hi",
description: "say hi",
run: (interaction) => {
reacord.reply(interaction, <>hi</>)
},
},
])
```
## Ephemeral Command Replies
Ephemeral replies are replies that only appear for one user. To create them, use the `.ephemeralReply()` function.
```tsx
handleCommands(client, [
{
name: "pong",
description: "pong, but in secret",
run: (interaction) => {
reacord.ephemeralReply(interaction, <>(pong)</>)
},
},
])
```
The `ephemeralReply` function also returns an instance, but ephemeral replies cannot be updated via `instance.render()`. You can `.deactivate()` them, but `.destroy()` will not delete the message; only the user can hide it from view.

View File

@@ -0,0 +1,29 @@
import packageJson from "reacord/package.json"
import LandingExample from "~/modules/landing/landing-example.mdx"
import { MainNavigation } from "~/modules/navigation/main-navigation"
import { maxWidthContainer } from "~/modules/ui/components"
export default function Landing() {
return (
<div className="flex flex-col min-w-0 min-h-screen text-center">
<header className={maxWidthContainer}>
<MainNavigation />
</header>
<div className="px-4 pb-8 flex flex-1">
<main className="px-4 py-6 rounded-lg shadow bg-slate-800 space-y-5 m-auto w-full max-w-xl">
<h1 className="text-6xl font-light">reacord</h1>
<section className="mx-auto text-sm sm:text-base">
<LandingExample />
</section>
<p className="text-2xl font-light">{packageJson.description}</p>
<a
href="/guides/getting-started"
className="inline-block px-4 py-3 text-xl transition rounded-lg bg-emerald-700 hover:translate-y-[-2px] hover:bg-emerald-800 hover:shadow"
>
Get Started
</a>
</main>
</div>
</div>
)
}

View File

@@ -0,0 +1,5 @@
import type { LoaderFunction } from "remix"
import { serveTailwindCss } from "remix-tailwind"
export const loader: LoaderFunction = () =>
serveTailwindCss("app/modules/ui/tailwind.css")

View File

@@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost:3000/"
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,12 @@
export {}
describe("main popover menu", () => {
it("should toggle on button click", () => {
cy.viewport(480, 720)
cy.visit("/")
cy.findByRole("button", { name: "Menu" }).click()
cy.findByRole("menu").should("be.visible")
cy.findByRole("button", { name: "Menu" }).click()
cy.findByRole("menu").should("not.exist")
})
})

View File

@@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
export default function cypressConfig(on, config) {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -0,0 +1,26 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import "@testing-library/cypress/add-commands"

View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js 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'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress", "@testing-library/cypress"]
},
"include": ["."],
"exclude": []
}

View File

@@ -0,0 +1,51 @@
{
"private": true,
"name": "website",
"scripts": {
"prepare": "remix setup node",
"dev": "concurrently 'typedoc --watch' 'remix dev'",
"test": "node ./scripts/test.js",
"test-dev": "pnpm dev & wait-on http-get://localhost:3000 && cypress open",
"build": "typedoc && remix build",
"start": "remix-serve build",
"typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit"
},
"dependencies": {
"@heroicons/react": "^1.0.5",
"@remix-run/react": "^1.1.1",
"@remix-run/serve": "^1.1.1",
"@tailwindcss/typography": "^0.5.0",
"autoprefixer": "^10.4.2",
"clsx": "^1.1.1",
"fast-glob": "^3.2.10",
"gray-matter": "^4.0.3",
"postcss": "^8.4.5",
"reacord": "workspace:*",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"remix": "^1.1.1",
"remix-tailwind": "^0.2.1",
"tailwindcss": "^3.0.12"
},
"devDependencies": {
"@remix-run/dev": "^1.1.1",
"@remix-run/node": "^1.1.1",
"@testing-library/cypress": "^8.0.2",
"@types/node": "*",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/tailwindcss": "^3.0.2",
"@types/wait-on": "^5.3.1",
"concurrently": "^7.0.0",
"cypress": "^9.2.1",
"execa": "^6.0.0",
"rehype-prism-plus": "^1.3.0",
"typedoc": "^0.22.10",
"typescript": "^4.5.4",
"wait-on": "^6.0.0"
},
"engines": {
"node": ">=14"
},
"sideEffects": false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,18 @@
/* eslint-disable unicorn/prefer-module */
/**
* @type {import('@remix-run/dev/config').AppConfig}
*/
module.exports = {
appDirectory: "app",
assetsBuildDirectory: "public/build",
publicPath: "/build/",
serverBuildDirectory: "build",
devServerPort: 8002,
ignoredRouteFiles: [".*"],
mdx: async () => {
const rehypePrism = await import("rehype-prism-plus")
return {
rehypePlugins: [rehypePrism.default],
}
},
}

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,10 @@
import cypress from "cypress"
import { execa } from "execa"
import waitOn from "wait-on"
await execa("pnpm", ["build"], { stdio: "inherit" })
const app = execa("pnpm", ["start"], { stdio: "inherit" })
await waitOn({ resources: ["http-get://localhost:3000"] })
await cypress.run()
console.log("cypress run done")
app.kill()

View File

@@ -0,0 +1,15 @@
// @ts-nocheck
module.exports = {
content: ["./app/**/*.{ts,tsx,md}"],
theme: {
fontFamily: {
sans: ["Rubik", "sans-serif"],
monospace: ["'JetBrains Mono'", "monospace"],
},
boxShadow: {
DEFAULT: "0 2px 9px 0 rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3)",
},
extend: {},
},
plugins: [require("@tailwindcss/typography")],
}

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"paths": {
"~/*": ["./app/*"]
}
}
}

View File

@@ -0,0 +1,22 @@
{
"entryPoints": ["../reacord/library/main.ts"],
"out": ["public/api"],
"tsconfig": "../reacord/tsconfig.json",
"excludeInternal": true,
"excludePrivate": true,
"excludeProtected": true,
"categorizeByGroup": false,
"preserveWatchOutput": true,
"githubPages": false,
"readme": "none",
"categoryOrder": [
"Core",
"Embed",
"Button",
"Link",
"Select",
"Action Row",
"Component Event",
"*"
]
}