Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
70 changes: 69 additions & 1 deletion src/Elm/Kernel/Test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*

import Elm.Kernel.Utils exposing (Tuple0)
import File exposing (FileNotFound, GeneralFileError, IsDirectory, PathEscapesDirectory)
import Result exposing (Err, Ok)

*/


function _Test_runThunk(thunk)
{
try {
Expand All @@ -16,3 +16,71 @@ function _Test_runThunk(thunk)
return __Result_Err(err.toString());
}
}


const fs = require('fs');
const path = require('path');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably worth adding the node: prefix to these Node built-ins.
Node supports this, and Deno and Bun both have Node-compatible APIs that require it.
It might be worth testing with elm-test-rs --deno (https://github.com/mpizenberg/elm-test-rs?tab=readme-ov-file#deno-runtime)

Suggested change
const fs = require('fs');
const path = require('path');
const fs = require('node:fs');
const path = require('node:path');

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brian-carroll OK so I finally got around to looking into this and I was having trouble getting elm-test-rs --deno to run... have you ever done this successfully?

> elm-test-rs --deno ./tests/Page/Learn/GuidedDrafts/MainSpec.elm 
Error: Deno supervisor failed to start

Caused by:
    No such file or directory (os error 2)

Copy link
Copy Markdown

@brian-carroll brian-carroll Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't tried it when I made the comment.
I tried it just now and got the same message... and realised it meant I didn't have Deno installed! 😆
What do you get for which deno?

I installed it, tried again, and got an error that suggests elm-test-rs has not been updated to support more recent versions of Deno. Deno support seems to have been added 4 years ago. The current Deno version is 2.5.4. The latest v1 release is 1.46.3. elm-test-rs seems to have errors with both.

I still think importing from node:fs instead of just fs is the correct fix. It constitutes full support for both Deno and Bun as far as this repo is concerned, based on their own docs:
Deno's docs on Node compatibility
Bun's docs on Node compatibility

Beyond that, if users are still having issues, the problem is in another repo such as elm-test-rs.

Details
❯ elm-test-rs --deno ./tests/Page/Learn/GuidedDrafts/MainSpec.elm
Warning `allow-hrtime` and `deny-hrtime` have been removed in Deno 2, as high resolution time is now always allowed
error: Uncaught (in promise) TypeError: Deno.read is not a function
    const num = await Deno.read(rid, buf);
                           ^
    at _readTillDone (file:///Users/briancarroll/Documents/NoRedInk/monolith/ui/elm-stuff/tests-0.19.1/js/deno_linereader.mjs:6:28)
    at Object.next (file:///Users/briancarroll/Documents/NoRedInk/monolith/ui/elm-stuff/tests-0.19.1/js/deno_linereader.mjs:25:38)
    at file:///Users/briancarroll/Documents/NoRedInk/monolith/ui/elm-stuff/tests-0.19.1/js/deno_supervisor.mjs:108:16


function _Test_readFile(filePath)
{
// Test for this early as `resolve` will strip training slashes
if (filePath.slice(-1) == path.sep) {
return __Result_Err(__File_IsDirectory);
}

// Protect against reading files above the "tests" directory
const testsPath = path.resolve("tests");
const fullPath = path.resolve(testsPath, filePath);

if (!fullPath.startsWith(testsPath))
{
return __Result_Err(__File_PathEscapesDirectory);
}

try {
return __Result_Ok(fs.readFileSync(fullPath, { encoding: 'utf8' }));
}
catch (err)
{
if (err.code == "ENOENT"){
return __Result_Err(__File_FileNotFound);
}
else {
return __Result_Err(__File_GeneralFileError(err.toString()));
}
}
}

var _Test_writeFile = F2(function(filePath, contents)
{
// Test for this early as `resolve` will strip training slashes
if (filePath.slice(-1) == path.sep) {
return __Result_Err(__File_IsDirectory);
}

// Protect against writing files above the "tests" directory
const testsPath = path.resolve("tests");
const fullPath = path.resolve(testsPath, filePath);

if (!fullPath.startsWith(testsPath))
{
return __Result_Err(__File_PathEscapesDirectory);
}

const fullDir = path.dirname(fullPath);

if (!fs.existsSync(fullDir))
{
// Can this make a nested directory?
fs.mkdirSync(fullDir, {recursive: true});
}
Comment thread
micahhahn marked this conversation as resolved.
Outdated

try {
fs.writeFileSync(fullPath, contents);
return __Result_Ok(__Utils_Tuple0);
}
catch (err)
{
return __Result_Err(__File_GeneralFileError(err.toString()));
}
})
42 changes: 41 additions & 1 deletion src/Expect.elm
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Expect exposing
, lessThan, atMost, greaterThan, atLeast
, FloatingPointTolerance(..), within, notWithin
, ok, err, equalLists, equalDicts, equalSets
, pass, fail, onFail
, pass, fail, onFail, equalToFile
)

{-| A library to create `Expectation`s, which describe a claim to be tested.
Expand Down Expand Up @@ -42,6 +42,9 @@ or both. For an in-depth look, see our [Guide to Floating Point Comparison](#gui

@docs ok, err, equalLists, equalDicts, equalSets

## Golden Files

@docs equalToFile

## Customizing

Expand Down Expand Up @@ -104,6 +107,7 @@ Another example is comparing values that are on either side of zero. `0.0001` is

import Dict exposing (Dict)
import Set exposing (Set)
import File
import Test.Distribution
import Test.Expectation
import Test.Internal as Internal
Expand Down Expand Up @@ -576,6 +580,42 @@ equalSets expected actual =
reportCollectionFailure "Expect.equalSets" expected actual missingKeys extraKeys


{-| Tests the a String is equal to the contents of the file stored at the file path.

If the file does not exist, it will be created and this test will pass.

If the file does exist, then this test will pass if its contents are equal to the actual string.

All file paths are scoped to be within the "tests/" directory.

-}
equalToFile : String -> String -> Expectation
equalToFile filePath actual =
case File.readFile filePath of
Err File.FileNotFound ->
case File.writeFile filePath actual of
Err (File.GeneralFileError fileError) ->
Test.Expectation.fail { description = "Expect.equalToFile encountered a general file error: " ++ fileError, reason = Custom }

-- This case should be impossible non general file errors should have been surfaced in the call to `readFile` above.
Err _ ->
Test.Expectation.fail { description = "Expect.equalToFile encountered an unexpected error", reason = Custom }

Ok _ ->
pass

Err File.IsDirectory ->
Test.Expectation.fail { description = "Expect.equalToFile was given a directory instead of a file", reason = Custom }

Err File.PathEscapesDirectory ->
Test.Expectation.fail { description = "Expect.equalToFile was given a path that would escape the tests/ directory", reason = Custom }

Err (File.GeneralFileError fileError) ->
Test.Expectation.fail { description = "Expect.equalToFile encountered a general file error: " ++ fileError, reason = Custom }

Ok contents ->
equateWith ("equalToFile \'" ++ filePath ++ "\'") (==) contents actual

{-| Always passes.

import Json.Decode exposing (decodeString, int)
Expand Down
15 changes: 15 additions & 0 deletions src/File.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module File exposing (readFile, writeFile, FileError(..))

import Elm.Kernel.Test

type FileError
= FileNotFound
| IsDirectory
| PathEscapesDirectory
| GeneralFileError String

readFile : String -> Result FileError String
readFile = Elm.Kernel.Test.readFile

writeFile : String -> String -> Result FileError ()
writeFile = Elm.Kernel.Test.writeFile
18 changes: 18 additions & 0 deletions src/Test/Html/Query.elm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Test.Html.Query exposing
( Single, Multiple, fromHtml
, find, findAll, children, first, index, keep
, count, contains, has, hasNot, each
, prettyPrintSingle
)

{-| Querying HTML structure.
Expand All @@ -18,6 +19,10 @@ module Test.Html.Query exposing

@docs count, contains, has, hasNot, each

## Debugging

@docs prettyPrintSingle

-}

import Expect exposing (Expectation)
Expand Down Expand Up @@ -496,3 +501,16 @@ each : (Single msg -> Expectation) -> Multiple msg -> Expectation
each check (Internal.Multiple showTrace query) =
Internal.expectAll check query
|> failWithQuery showTrace "Query.each" query

{-| Pretty prints the result of a query as HTML if successful -}
prettyPrintSingle : Single msg -> Result String String
prettyPrintSingle (Internal.Single _ query) =
case Internal.traverse query of
Ok [ element ] ->
Ok <| Internal.prettyPrint element

Ok results ->
Err <| "Query.prettyPrintSingle expected exactly one result from query, but found " ++ String.fromInt (List.length results)

Err queryError ->
Err <| "Query.prettyPrintSingle " ++ Internal.queryErrorToString queryError