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

init react component tests 🎉 #579

Merged
merged 3 commits into from
Nov 25, 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
6 changes: 1 addition & 5 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ insert_final_newline = false
fsharp_multiline_bracket_style = stroustrup
fsharp_newline_before_multiline_computation_expression = false

[*.fsproj]
[*.{fsproj,yml,json,ts,tsx}]
indent_style = space
indent_size = 2

[*.yml]
indent_style = space
indent_size = 2
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ release.sh
/.db/neo4j
/src/Client/output
/tests/Client/output
/tests/Components/output
/.paket

1 change: 1 addition & 0 deletions build/Build.fs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ module Tests =
"server", dotnet [ "run" ] serverTestsPath
"client", dotnet [ "fable"; "-o"; "output"; "-s"; "--run"; "npx"; "mocha"; $"{clientTestsPath}/output/Client.Tests.js" ] clientTestsPath
]|> runParallel
run npm [ "run"; "test:run" ] "."

Target.create "Format" (fun _ ->
run dotnet [ "fantomas"; "."; "-r" ] "src"
Expand Down
3,772 changes: 2,687 additions & 1,085 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@
"node": "~18 || ~20",
"npm": "~9 || ~10"
},
"scripts": {},
"scripts": {
"test": "dotnet fable watch ./src/Components/Components.fsproj --lang ts -e tsx -o ./tests/Components/output --run vitest -r ./tests/Components",
"pretest:run": "dotnet fable ./src/Components/Components.fsproj --lang ts -e tsx -o ./tests/Components/output --noCache",
"test:run": "vitest run -r ./tests/Components"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@types/node": "^20.10.3",
"@types/react": "^18.3.12",
"@types/testing-library__react": "^10.0.1",
"@vitejs/plugin-basic-ssl": "^1.0.2",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"daisyui": "^4.12.14",
"jsdom": "^25.0.1",
"mocha": "^10.8.2",
"office-addin-mock": "^2.4.6",
"postcss": "^8.4.39",
Expand All @@ -20,7 +29,8 @@
"tailwindcss": "^3.4.4",
"typescript": "^5.6.3",
"vite": "^5.0.5",
"vite-plugin-node-polyfills": "^0.22.0"
"vite-plugin-node-polyfills": "^0.22.0",
"vitest": "^2.1.5"
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
Expand Down
16 changes: 8 additions & 8 deletions src/Client/MainComponents/Navbar.fs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ let private QuickAccessButtonListStart (state: LocalHistory.Model) dispatch =
Spreadsheet.UpdateHistoryPosition newPosition |> Msg.SpreadsheetMsg |> dispatch
),
isDisabled = (state.NextPositionIsValid(state.HistoryCurrentPosition + 1) |> not)
)
) |> toReact
QuickAccessButton.QuickAccessButton(
"Forward",
React.fragment [
Expand All @@ -61,7 +61,7 @@ let private QuickAccessButtonListStart (state: LocalHistory.Model) dispatch =
Spreadsheet.UpdateHistoryPosition newPosition |> Msg.SpreadsheetMsg |> dispatch
),
isDisabled = (state.NextPositionIsValid(state.HistoryCurrentPosition - 1) |> not)
)
) |> toReact
]
]

Expand All @@ -83,7 +83,7 @@ let private QuickAccessButtonListEnd (model: Model) dispatch =
Spreadsheet.ExportXlsx model.SpreadsheetModel.ArcFile.Value |> SpreadsheetMsg |> dispatch
| _ -> ()
)
)
) |> toReact
match model.PersistentStorageState.Host with
| Some Swatehost.Browser ->
QuickAccessButton.QuickAccessButton(
Expand All @@ -93,7 +93,7 @@ let private QuickAccessButtonListEnd (model: Model) dispatch =
],
(fun _ -> Modals.Controller.renderModal("ResetTableWarning", Modals.ResetTable.Main dispatch)),
classes = "hover:!text-error"
)
) |> toReact
NavbarBurger.Main(model, dispatch)
| _ ->
Html.none
Expand All @@ -111,7 +111,7 @@ let private WidgetNavbarList (model, dispatch, addWidget: Widget -> unit) =
]
],
(fun _ -> addWidget Widget._BuildingBlock)
)
) |> toReact
let addTemplate =
QuickAccessButton.QuickAccessButton(
"Add Template",
Expand All @@ -120,23 +120,23 @@ let private WidgetNavbarList (model, dispatch, addWidget: Widget -> unit) =
Html.i [prop.className "fa-solid fa-table" ]
],
(fun _ -> addWidget Widget._Template)
)
) |> toReact
let filePicker =
QuickAccessButton.QuickAccessButton(
"File Picker",
React.fragment [
Html.i [prop.className "fa-solid fa-file-signature" ]
],
(fun _ -> addWidget Widget._FilePicker)
)
) |> toReact
let dataAnnotator =
QuickAccessButton.QuickAccessButton(
"Data Annotator",
React.fragment [
Html.i [prop.className "fa-solid fa-object-group" ]
],
(fun _ -> addWidget Widget._DataAnnotator)
)
) |> toReact
Html.div [
prop.className "flex flex-row"
prop.children [
Expand Down
2 changes: 1 addition & 1 deletion src/Client/SharedComponents/NavbarBurger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ type NavbarBurger =
"More",
NavbarBurger.Dropdown(isOpen, setIsOpen, model, dispatch),
(fun _ -> setIsOpen (not isOpen))
)
) |> toReact
12 changes: 6 additions & 6 deletions src/Client/SidebarComponents/Navbar.fs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ let private QuickAccessList toggleMetdadataModal model (dispatch: Messages.Msg -
Html.i [prop.className "fa-solid fa-info"]
],
toggleMetdadataModal
)
) |> toReact

QuickAccessButton.QuickAccessButton(
"Create Annotation Table",
Expand All @@ -227,7 +227,7 @@ let private QuickAccessList toggleMetdadataModal model (dispatch: Messages.Msg -
let ctrl = e.metaKey || e.ctrlKey
SpreadsheetInterface.CreateAnnotationTable ctrl |> InterfaceMsg |> dispatch
)
)
) |> toReact
match model.PersistentStorageState.Host with
| Some Swatehost.Excel ->
QuickAccessButton.QuickAccessButton(
Expand All @@ -241,7 +241,7 @@ let private QuickAccessList toggleMetdadataModal model (dispatch: Messages.Msg -
let ctrl = not (e.metaKey || e.ctrlKey)
OfficeInterop.AutoFitTable ctrl |> OfficeInteropMsg |> dispatch
)
)
) |> toReact
| _ ->
()
QuickAccessButton.QuickAccessButton(
Expand All @@ -254,15 +254,15 @@ let private QuickAccessList toggleMetdadataModal model (dispatch: Messages.Msg -
(fun _ ->
SpreadsheetInterface.RectifyTermColumns |> InterfaceMsg |> dispatch
)
)
) |> toReact
QuickAccessButton.QuickAccessButton(
"Remove Building Block",
React.fragment [
Html.i [prop.className "fa-solid fa-minus pr-1"]
Html.i [prop.className "fa-solid fa-table-columns"]
],
(fun _ -> SpreadsheetInterface.RemoveBuildingBlock |> InterfaceMsg |> dispatch)
)
) |> toReact
QuickAccessButton.QuickAccessButton(
"Get Building Block Information",
React.fragment [
Expand All @@ -271,7 +271,7 @@ let private QuickAccessList toggleMetdadataModal model (dispatch: Messages.Msg -
Html.i [prop.className "fa-solid fa-table-columns"]
],
(fun _ -> SpreadsheetInterface.GetBuildingBlockDetails |> InterfaceMsg |> dispatch)
)
) |> toReact
]
|> React.fragment

Expand Down
2 changes: 2 additions & 0 deletions src/Components/Components.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
</ItemGroup>

<ItemGroup>
<Compile Include="Util.fs" />
<Compile Include="Example.fs" />
<Compile Include="QuickAccessButton.fs" />
</ItemGroup>

Expand Down
33 changes: 33 additions & 0 deletions src/Components/Example.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Components

open Fable.Core
open Feliz


/// These 2 Attributes will remove the type from the generated JS code
/// ⚠️ Due to [<Mangle(false)>] you can never use overloads in the same file!
[<Erase; Mangle(false)>]
type TestInput =

/// [<ExportDefault>] can only be used on a single member in a module
/// [<NamedParams>] is used for correct type hinting in typescript
/// with: export function TestInput({ children, number }: {children?: ReactElement, number?: int32 }): any {
/// without: export function TestInput(children?: ReactElement, number?: int32): any {
/// ⚠️ ... fails because react requires object as input
[<ExportDefault; NamedParams>]
static member TestInput(?children: ReactElement, ?number: int) : JSX.Element =
let state, useState = React.useState (number |> Option.defaultValue 0)
Html.div [
prop.children [
if children.IsSome then children.Value
Html.div [
prop.textf "Number: %d" state
]
Html.input [
prop.type'.number
prop.onChange(fun (number:int) -> useState number)
]
]
]
// 👀 This is used to remove type hint to: ReactElement in typescript. Which would trigger warning when using in .tsx file.
|> unbox<JSX.Element>
14 changes: 8 additions & 6 deletions src/Components/QuickAccessButton.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ open Browser.Types
[<Erase; Mangle(false)>]
type QuickAccessButton =

[<NamedParams; ReactComponent(true)>]
static member QuickAccessButton(desc:string, children: ReactElement, onclick: Event -> unit, ?isDisabled, ?props, ?classes: string) =
[<ExportDefault; NamedParams>]
static member QuickAccessButton(
desc:string, children: ReactElement, onclick: Event -> unit,
?isDisabled: bool, ?props: IReactProperty seq, ?classes: string
) =
let isDisabled = defaultArg isDisabled false
Html.button [
prop.className [
Expand All @@ -20,7 +23,6 @@ type QuickAccessButton =
prop.disabled isDisabled
prop.onClick onclick
if props.IsSome then yield! props.Value
prop.children [
children
]
]
prop.children children
]
|> unbox<JSX.Element>
22 changes: 22 additions & 0 deletions src/Components/Util.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Components

open Fable.Core
open Feliz

/// https://fable.io/blog/2022/2022-10-12-react-jsx.html
[<AutoOpen>]
module Util =

let inline toJsx (el: ReactElement) : JSX.Element = unbox el
let inline toReact (el: JSX.Element) : ReactElement = unbox el

/// Enables use of Feliz styles within a JSX hole
let inline toStyle (styles: IStyleAttribute list) : obj = JsInterop.createObj (unbox styles)
let toClass (classes: (string * bool) list) : string =
classes
|> List.choose (fun (c, b) ->
match c.Trim(), b with
| "", _
| _, false -> None
| c, true -> Some c)
|> String.concat " "
15 changes: 15 additions & 0 deletions tests/Components/Base.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, test, expect } from 'vitest';

describe('Native DOM', () => {
test('creates an element in the DOM', () => {
const div = document.createElement('div');
expect(div).toBeDefined();
});


test('environment should be jsdom', () => {
expect(globalThis.navigator).toBeDefined();
expect(globalThis.navigator.userAgent).toContain('jsdom');
});

});
58 changes: 58 additions & 0 deletions tests/Components/Example.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect } from 'vitest';

// Assuming TestInput is the transpiled component from Fable
import Example from './output/Example'; // Update the import path as needed

describe('TestInput Component', () => {
it('renders with initial number and children', () => {
render(
<Example number={42}>
<span>Child Element</span>
</Example>
);

// Check for child element
expect(screen.getByText('Child Element')).toBeInTheDocument();

// Check for initial number
expect(screen.getByText('Number: 42')).toBeInTheDocument();
});

it('renders with default number (0) when no number prop is provided', () => {
render(
<Example>
<span>Default Number</span>
</Example>
);

// Check for default number
expect(screen.getByText('Number: 0')).toBeInTheDocument();
});

it('updates number when input value changes', async () => {
render(<Example number={10} />);

// Get the number input
const input = screen.getByRole('spinbutton'); // Default ARIA role for number inputs

// Simulate changing the input value
fireEvent.change(input, { target: { value: '25' } });

// Check if the number is updated
expect(screen.getByText('Number: 25')).toBeInTheDocument();
});

it('handles non-numeric input gracefully', async () => {
render(<Example />);

// Get the number input
const input = screen.getByRole('spinbutton');

// Simulate entering non-numeric value
fireEvent.change(input, { target: { value: 'not-a-number' } });

// Check if the number remains 0 (default state)
expect(screen.getByText('Number: 0')).toBeInTheDocument();
});
});
Loading
Loading