@curtissimo/elm-hot 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG +40 -0
- package/CODE_OF_CONDUCT.md +74 -0
- package/CONTRIBUTING.md +51 -0
- package/LICENSE.txt +33 -0
- package/README.md +84 -0
- package/package.json +37 -0
- package/resources/hmr.js +519 -0
- package/src/index.js +6 -0
- package/src/inject.js +46 -0
- package/test/client.js +63 -0
- package/test/fixtures/BrowserApplicationCounter.elm +136 -0
- package/test/fixtures/BrowserApplicationCounter.html +22 -0
- package/test/fixtures/BrowserApplicationCounterDeepKey.elm +137 -0
- package/test/fixtures/BrowserApplicationCounterDeepKey.html +18 -0
- package/test/fixtures/BrowserApplicationCounterMultiKey.elm +158 -0
- package/test/fixtures/BrowserApplicationCounterMultiKey.html +22 -0
- package/test/fixtures/BrowserApplicationMissingNavKeyError.elm +62 -0
- package/test/fixtures/BrowserApplicationMissingNavKeyError.html +22 -0
- package/test/fixtures/BrowserApplicationNavKeyMoved.elm +159 -0
- package/test/fixtures/BrowserApplicationNavKeyMoved.html +22 -0
- package/test/fixtures/BrowserApplicationWithNull.elm +142 -0
- package/test/fixtures/BrowserApplicationWithNull.html +22 -0
- package/test/fixtures/BrowserDocumentCounter.elm +57 -0
- package/test/fixtures/BrowserDocumentCounter.html +24 -0
- package/test/fixtures/BrowserElementCounter.elm +55 -0
- package/test/fixtures/BrowserElementCounter.html +24 -0
- package/test/fixtures/BrowserSandboxCounter.elm +48 -0
- package/test/fixtures/BrowserSandboxCounter.html +21 -0
- package/test/fixtures/DebugBrowserApplication.elm +139 -0
- package/test/fixtures/DebugBrowserApplication.html +22 -0
- package/test/fixtures/DebugEmbed.elm +48 -0
- package/test/fixtures/DebugEmbed.html +21 -0
- package/test/fixtures/DebugFullscreen.elm +57 -0
- package/test/fixtures/DebugFullscreen.html +22 -0
- package/test/fixtures/FullScreenEmptyInit.elm +51 -0
- package/test/fixtures/FullScreenEmptyInit.html +19 -0
- package/test/fixtures/InitSideEffects.elm +65 -0
- package/test/fixtures/InitSideEffects.html +27 -0
- package/test/fixtures/MainWithTasks.elm +70 -0
- package/test/fixtures/MainWithTasks.html +21 -0
- package/test/fixtures/MultiMain.html +21 -0
- package/test/fixtures/MultiMain1.elm +48 -0
- package/test/fixtures/MultiMain2.elm +48 -0
- package/test/fixtures/PortsEmbed.elm +64 -0
- package/test/fixtures/PortsEmbed.html +27 -0
- package/test/fixtures/PortsFullscreen.elm +70 -0
- package/test/fixtures/PortsFullscreen.html +26 -0
- package/test/fixtures/build.sh +29 -0
- package/test/fixtures/elm.json +24 -0
- package/test/server.js +73 -0
- package/test/test.js +320 -0
- package/test.sh +16 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
port module PortsEmbed exposing (main)
|
|
2
|
+
|
|
3
|
+
import Browser
|
|
4
|
+
import Html exposing (Html, button, div, h1, p, span, text)
|
|
5
|
+
import Html.Attributes exposing (id)
|
|
6
|
+
import Html.Events exposing (onClick)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
main : Program () Model Msg
|
|
10
|
+
main =
|
|
11
|
+
Browser.element
|
|
12
|
+
{ init = init
|
|
13
|
+
, view = view
|
|
14
|
+
, update = update
|
|
15
|
+
, subscriptions = subscriptions
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
port toJavaScript : Int -> Cmd msg
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
port fromJavaScript : (Int -> msg) -> Sub msg
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
type alias Model =
|
|
26
|
+
{ count : Int }
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
init : () -> ( Model, Cmd Msg )
|
|
30
|
+
init _ =
|
|
31
|
+
( { count = 0 }, Cmd.none )
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
type Msg
|
|
35
|
+
= Increment
|
|
36
|
+
| GotNewValue Int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
update : Msg -> Model -> ( Model, Cmd msg )
|
|
40
|
+
update msg model =
|
|
41
|
+
case msg of
|
|
42
|
+
Increment ->
|
|
43
|
+
( model, toJavaScript model.count )
|
|
44
|
+
|
|
45
|
+
GotNewValue _ ->
|
|
46
|
+
( { model | count = model.count + 1 }, Cmd.none )
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
subscriptions : Model -> Sub Msg
|
|
50
|
+
subscriptions _ =
|
|
51
|
+
fromJavaScript GotNewValue
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
view : Model -> Html Msg
|
|
55
|
+
view model =
|
|
56
|
+
div []
|
|
57
|
+
[ h1 [] [ text "PortsEmbed" ]
|
|
58
|
+
, span [ id "code-version" ] [ text "code: v1" ]
|
|
59
|
+
, p []
|
|
60
|
+
[ text "Counter value is: "
|
|
61
|
+
, span [ id "counter-value" ] [ text (String.fromInt model.count) ]
|
|
62
|
+
]
|
|
63
|
+
, button [ onClick Increment, id "counter-button" ] [ text "+" ]
|
|
64
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Main</title>
|
|
7
|
+
<script type="text/javascript" src="client.js"></script>
|
|
8
|
+
<script type="text/javascript" src="build/PortsEmbed.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div id="embed">to be filled out later</div>
|
|
13
|
+
<script>
|
|
14
|
+
connect("PortsEmbed");
|
|
15
|
+
var app = Elm.PortsEmbed.init({
|
|
16
|
+
node: document.getElementById('embed'),
|
|
17
|
+
flags: {}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
app.ports.toJavaScript.subscribe(function (n) {
|
|
21
|
+
console.log("received " + n + " from Elm");
|
|
22
|
+
app.ports.fromJavaScript.send(n);
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
</body>
|
|
26
|
+
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
port module PortsFullscreen exposing (..)
|
|
2
|
+
|
|
3
|
+
import Browser
|
|
4
|
+
import Html exposing (button, h1, p, span, text)
|
|
5
|
+
import Html.Attributes exposing (id)
|
|
6
|
+
import Html.Events exposing (onClick)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
main : Program Flags Model Msg
|
|
10
|
+
main =
|
|
11
|
+
Browser.document
|
|
12
|
+
{ init = init
|
|
13
|
+
, view = view
|
|
14
|
+
, update = update
|
|
15
|
+
, subscriptions = subscriptions
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
port toJavaScript : Int -> Cmd msg
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
port fromJavaScript : (Int -> msg) -> Sub msg
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
type alias Flags =
|
|
26
|
+
{ n : Int }
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
type alias Model =
|
|
30
|
+
{ count : Int }
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
init : Flags -> ( Model, Cmd Msg )
|
|
34
|
+
init flags =
|
|
35
|
+
( { count = flags.n }, Cmd.none )
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
type Msg
|
|
39
|
+
= Increment
|
|
40
|
+
| GotNewValue Int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
update : Msg -> Model -> ( Model, Cmd msg )
|
|
44
|
+
update msg model =
|
|
45
|
+
case msg of
|
|
46
|
+
Increment ->
|
|
47
|
+
( model, toJavaScript model.count )
|
|
48
|
+
|
|
49
|
+
GotNewValue _ ->
|
|
50
|
+
( { model | count = model.count + 1 }, Cmd.none )
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
subscriptions : Model -> Sub Msg
|
|
54
|
+
subscriptions _ =
|
|
55
|
+
fromJavaScript GotNewValue
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
view : Model -> Browser.Document Msg
|
|
59
|
+
view model =
|
|
60
|
+
{ title = "PortsFullscreen"
|
|
61
|
+
, body =
|
|
62
|
+
[ h1 [] [ text "PortsFullscreen" ]
|
|
63
|
+
, span [ id "code-version" ] [ text "code: v1" ]
|
|
64
|
+
, p []
|
|
65
|
+
[ text "Counter value is: "
|
|
66
|
+
, span [ id "counter-value" ] [ text (String.fromInt model.count) ]
|
|
67
|
+
]
|
|
68
|
+
, button [ onClick Increment, id "counter-button" ] [ text "+" ]
|
|
69
|
+
]
|
|
70
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Main</title>
|
|
7
|
+
<script type="text/javascript" src="client.js"></script>
|
|
8
|
+
<script type="text/javascript" src="build/PortsFullscreen.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<script>
|
|
13
|
+
connect("PortsFullscreen");
|
|
14
|
+
var app = Elm.PortsFullscreen.init({
|
|
15
|
+
flags: {
|
|
16
|
+
n: 0
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
app.ports.toJavaScript.subscribe(function (n) {
|
|
20
|
+
console.log("received " + n + " from Elm");
|
|
21
|
+
app.ports.fromJavaScript.send(n);
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
</body>
|
|
25
|
+
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
cd "${0%/*}" # set CWD to the dir containing the script
|
|
4
|
+
|
|
5
|
+
set -o errexit
|
|
6
|
+
set -o pipefail
|
|
7
|
+
set -o nounset
|
|
8
|
+
|
|
9
|
+
for filename in *.elm; do
|
|
10
|
+
|
|
11
|
+
if [[ $filename == MultiMain* ]] ;
|
|
12
|
+
then
|
|
13
|
+
# these will be compiled later as a single unit
|
|
14
|
+
continue
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
extraArgs=""
|
|
18
|
+
|
|
19
|
+
if [[ $filename == Debug* ]] ;
|
|
20
|
+
then
|
|
21
|
+
extraArgs="--debug"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo "Compiling $filename"
|
|
25
|
+
elm make $filename --output=build/"$(basename "$filename" .elm).js" ${extraArgs}
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
echo "Compiling MultiMain1.elm and MultiMain2.elm"
|
|
29
|
+
elm make MultiMain1.elm MultiMain2.elm --output=build/MultiMain.js
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "application",
|
|
3
|
+
"source-directories": [
|
|
4
|
+
"."
|
|
5
|
+
],
|
|
6
|
+
"elm-version": "0.19.1",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"direct": {
|
|
9
|
+
"elm/browser": "1.0.2",
|
|
10
|
+
"elm/core": "1.0.2",
|
|
11
|
+
"elm/html": "1.0.0",
|
|
12
|
+
"elm/json": "1.1.3",
|
|
13
|
+
"elm/url": "1.0.0"
|
|
14
|
+
},
|
|
15
|
+
"indirect": {
|
|
16
|
+
"elm/time": "1.0.0",
|
|
17
|
+
"elm/virtual-dom": "1.0.2"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"test-dependencies": {
|
|
21
|
+
"direct": {},
|
|
22
|
+
"indirect": {}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/test/server.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const chokidar = require("chokidar");
|
|
5
|
+
const app = express();
|
|
6
|
+
|
|
7
|
+
app.setMaxListeners(30);
|
|
8
|
+
|
|
9
|
+
const { inject } = require("../src/inject.js");
|
|
10
|
+
|
|
11
|
+
const pathToTestFixtures = path.join(__dirname, "./fixtures");
|
|
12
|
+
const pathToBuildDir = path.join(pathToTestFixtures, "build");
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
fs.mkdirSync(pathToBuildDir);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error.code !== "EEXIST") throw error;
|
|
18
|
+
}
|
|
19
|
+
const watcher = chokidar.watch(pathToBuildDir, { persistent: true });
|
|
20
|
+
watcher.setMaxListeners(30);
|
|
21
|
+
|
|
22
|
+
app.get("/client.js", (req, res) =>
|
|
23
|
+
res.sendFile(path.join(__dirname, "./client.js")),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
app.get("/:filename.html", (req, res) => {
|
|
27
|
+
const filename = req.params.filename + ".html";
|
|
28
|
+
res.sendFile(path.join(pathToTestFixtures, filename));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
app.get("/build/:filename.js", function (req, res) {
|
|
32
|
+
const filename = req.params.filename + ".js";
|
|
33
|
+
const pathToElmCodeJS = path.join(pathToBuildDir, filename);
|
|
34
|
+
const originalElmCodeJS = fs.readFileSync(pathToElmCodeJS, {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
});
|
|
37
|
+
const fullyInjectedCode = inject(originalElmCodeJS);
|
|
38
|
+
res.send(fullyInjectedCode);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
app.get("/stream-:programName", function (req, res) {
|
|
42
|
+
const programName = req.params.programName;
|
|
43
|
+
res.writeHead(200, {
|
|
44
|
+
Connection: "keep-alive",
|
|
45
|
+
"Content-Type": "text/event-stream",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
watcher.on("change", function (pathThatChanged, stats) {
|
|
49
|
+
if (pathThatChanged.endsWith(programName + ".js")) {
|
|
50
|
+
//console.log("Pushing HMR event to client");
|
|
51
|
+
const relativeLoadPath = path.relative(
|
|
52
|
+
pathToTestFixtures,
|
|
53
|
+
pathThatChanged,
|
|
54
|
+
);
|
|
55
|
+
res.write(`data: ${relativeLoadPath}\n\n`);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function startServer(port) {
|
|
61
|
+
return app.listen(port);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (require.main === module) {
|
|
65
|
+
startServer(3000);
|
|
66
|
+
console.log("Server listening at http://127.0.0.1:3000");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
app,
|
|
71
|
+
watcher,
|
|
72
|
+
startServer: startServer,
|
|
73
|
+
};
|
package/test/test.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
|
|
4
|
+
const test = require("ava");
|
|
5
|
+
const puppeteer = require("puppeteer");
|
|
6
|
+
const childProcess = require("child_process");
|
|
7
|
+
|
|
8
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
|
|
10
|
+
const { watcher, startServer } = require("./server.js");
|
|
11
|
+
|
|
12
|
+
test.before(async () => {
|
|
13
|
+
console.log("Building the Elm code");
|
|
14
|
+
const output = childProcess.execFileSync("./build.sh", {
|
|
15
|
+
cwd: "./test/fixtures",
|
|
16
|
+
});
|
|
17
|
+
console.log("Elm build.sh output: " + output);
|
|
18
|
+
|
|
19
|
+
global.browser = await puppeteer.launch({
|
|
20
|
+
headless: true, // default is true; set to false when debugging failed tests
|
|
21
|
+
slowMo: 50, // introduce a little delay between each operation
|
|
22
|
+
dumpio: false, // default is false; set to true when debugging failed tests
|
|
23
|
+
args: ["--no-sandbox"], // required for CI builds
|
|
24
|
+
protocolTimeout: 360000,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test.beforeEach(async (t) => {
|
|
29
|
+
t.context.httpServer = startServer();
|
|
30
|
+
t.context.serverUrl =
|
|
31
|
+
"http://127.0.0.1:" + t.context.httpServer.address().port;
|
|
32
|
+
|
|
33
|
+
const page = await browser.newPage();
|
|
34
|
+
page.on("pageerror", (error) => {
|
|
35
|
+
console.log("BROWSER: uncaught exception: " + error);
|
|
36
|
+
});
|
|
37
|
+
page.on("requestfailed", (request) => {
|
|
38
|
+
console.log("BROWSER: request failed: " + request.url());
|
|
39
|
+
});
|
|
40
|
+
page.on("response", (response) => {
|
|
41
|
+
if (!response.ok())
|
|
42
|
+
console.error(
|
|
43
|
+
"BROWSER: response: " + response.url() + " " + response.status(),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
t.context.page = page;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.afterEach(async (t) => {
|
|
50
|
+
await t.context.page.close();
|
|
51
|
+
if (t.context.httpServer !== undefined) {
|
|
52
|
+
const server = t.context.httpServer;
|
|
53
|
+
await new Promise((good, bad) => {
|
|
54
|
+
server.close(good);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test.after.always(async () => {
|
|
60
|
+
if (typeof browser !== "undefined") {
|
|
61
|
+
// normally browser will be defined, but it might not be if a `before` hook failed
|
|
62
|
+
await browser.close();
|
|
63
|
+
await watcher.close();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
AVA tests are run concurrently in separate processes. This is good because
|
|
69
|
+
each integration test is slow. But you must also be careful to isolate
|
|
70
|
+
the tests (especially files on disk)!
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
test("counter HMR preserves count (Browser.element)", async (t) => {
|
|
74
|
+
await doCounterTest(t, "BrowserElementCounter");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("counter HMR preserves count (Browser.document)", async (t) => {
|
|
78
|
+
await doCounterTest(t, "BrowserDocumentCounter");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("counter HMR preserves count (Browser.sandbox)", async (t) => {
|
|
82
|
+
await doCounterTest(t, "BrowserSandboxCounter");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("counter HMR preserves count (Browser.application)", async (t) => {
|
|
86
|
+
await doBrowserApplicationTest(t, "BrowserApplicationCounter");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("Browser.Navigation.Key can be found in a nested record", async (t) => {
|
|
90
|
+
await doBrowserApplicationTest(t, "BrowserApplicationCounterDeepKey");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("Browser.Navigation.Key can be found in the variants of a union type", async (t) => {
|
|
94
|
+
await doBrowserApplicationTest(t, "BrowserApplicationCounterMultiKey");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("Browser.Navigation.Key can be found even when the keypath changes", async (t) => {
|
|
98
|
+
// see https://github.com/klazuka/elm-hot/issues/35
|
|
99
|
+
await doBrowserApplicationTest(t, "BrowserApplicationNavKeyMoved");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("init side effects do not run after HMR", async (t) => {
|
|
103
|
+
// see https://github.com/klazuka/elm-hot-webpack-loader/issues/1
|
|
104
|
+
await doCounterTest(t, "InitSideEffects");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("fullscreen apps which pass empty args to init works", async (t) => {
|
|
108
|
+
// see https://github.com/klazuka/elm-hot/issues/11
|
|
109
|
+
await doCounterTest(t, "FullScreenEmptyInit");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("multiple Elm Main modules", async (t) => {
|
|
113
|
+
const testName = "MultiMain";
|
|
114
|
+
const page = t.context.page;
|
|
115
|
+
await page.goto(`${t.context.serverUrl}/${testName}.html`);
|
|
116
|
+
|
|
117
|
+
const inc = "#incrementer ";
|
|
118
|
+
const dec = "#decrementer ";
|
|
119
|
+
|
|
120
|
+
// interleave various updates to the 2 separate Elm apps
|
|
121
|
+
// one app increments a counter; the other decrements
|
|
122
|
+
// each app has its own counter value.
|
|
123
|
+
await checkCodeVersion(t, page, "inc-v1", inc);
|
|
124
|
+
await checkCodeVersion(t, page, "dec-v1", dec);
|
|
125
|
+
await stepTheCounter(t, page, 1, inc);
|
|
126
|
+
await modifyElmCode(t, testName, page, "inc-v1", "inc-v2", inc);
|
|
127
|
+
await stepTheCounter(t, page, 2, inc);
|
|
128
|
+
await stepTheCounter(t, page, 3, inc);
|
|
129
|
+
await modifyElmCode(t, testName, page, "dec-v1", "dec-v2", dec);
|
|
130
|
+
await stepTheCounter(t, page, -1, dec);
|
|
131
|
+
await stepTheCounter(t, page, 4, inc);
|
|
132
|
+
await modifyElmCode(t, testName, page, "inc-v2", "inc-v3", inc);
|
|
133
|
+
await stepTheCounter(t, page, 5, inc);
|
|
134
|
+
await stepTheCounter(t, page, -2, dec);
|
|
135
|
+
await modifyElmCode(t, testName, page, "inc-v3", "inc-v4", inc);
|
|
136
|
+
await modifyElmCode(t, testName, page, "dec-v2", "dec-v3", dec);
|
|
137
|
+
await stepTheCounter(t, page, -3, dec);
|
|
138
|
+
await stepTheCounter(t, page, 6, inc);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("counter HMR preserves count (embed app in DOM with debugger)", async (t) => {
|
|
142
|
+
await doCounterTest(t, "DebugEmbed");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("counter HMR preserves count (fullscreen app in DOM with debugger)", async (t) => {
|
|
146
|
+
await doCounterTest(t, "DebugFullscreen");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("counter HMR preserves count (Browser.application with debugger)", async (t) => {
|
|
150
|
+
await doBrowserApplicationTest(t, "DebugBrowserApplication");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("pending async tasks are cancelled when HMR is performed", async (t) => {
|
|
154
|
+
const testName = "MainWithTasks";
|
|
155
|
+
const page = t.context.page;
|
|
156
|
+
await page.goto(`${t.context.serverUrl}/${testName}.html`);
|
|
157
|
+
const sleepyTaskMillis = 5000; // this MUST be in sync with the code in `MainWithTasks.elm`
|
|
158
|
+
const slop = 500; // additional millis to wait just to make sure that everything has completed
|
|
159
|
+
|
|
160
|
+
await checkCodeVersion(t, page, "v1");
|
|
161
|
+
t.is(await getCounterValue(page), 0);
|
|
162
|
+
|
|
163
|
+
// trigger sleepy increment but do HMR halfway through, cancelling the increment
|
|
164
|
+
await incrementCounter(page);
|
|
165
|
+
t.is(await getCounterValue(page), 0); // still 0 because the increment is async
|
|
166
|
+
await delay(sleepyTaskMillis / 2);
|
|
167
|
+
await modifyElmCode(t, testName, page, "v1", "v2");
|
|
168
|
+
await delay(sleepyTaskMillis / 2 + slop);
|
|
169
|
+
t.is(await getCounterValue(page), 0); // should still be 0 because the increment was cancelled
|
|
170
|
+
|
|
171
|
+
// trigger sleepy increment but this time allow it to complete
|
|
172
|
+
await incrementCounter(page);
|
|
173
|
+
await delay(sleepyTaskMillis + slop);
|
|
174
|
+
t.is(await getCounterValue(page), 1); // should now be 1 because the increment had time to finish
|
|
175
|
+
await modifyElmCode(t, testName, page, "v2", "v3");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("ports are reconnected after HMR (embed case)", async (t) => {
|
|
179
|
+
await doCounterTest(t, "PortsEmbed");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("ports are reconnected after HMR (fullscreen case)", async (t) => {
|
|
183
|
+
await doCounterTest(t, "PortsFullscreen");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("an Elm model containing `null` should not crash", async (t) => {
|
|
187
|
+
// see https://github.com/klazuka/elm-hot/pull/36
|
|
188
|
+
await doCounterTest(t, "BrowserApplicationWithNull");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
async function doCounterTest(t, testName) {
|
|
192
|
+
const page = t.context.page;
|
|
193
|
+
await page.goto(`${t.context.serverUrl}/${testName}.html`);
|
|
194
|
+
|
|
195
|
+
await checkCodeVersion(t, page, "v1");
|
|
196
|
+
await stepTheCounter(t, page, 1);
|
|
197
|
+
await modifyElmCode(t, testName, page, "v1", "v2");
|
|
198
|
+
await stepTheCounter(t, page, 2);
|
|
199
|
+
await modifyElmCode(t, testName, page, "v2", "v3");
|
|
200
|
+
await stepTheCounter(t, page, 3);
|
|
201
|
+
await stepTheCounter(t, page, 4);
|
|
202
|
+
await modifyElmCode(t, testName, page, "v3", "v4");
|
|
203
|
+
await stepTheCounter(t, page, 5);
|
|
204
|
+
await stepTheCounter(t, page, 6);
|
|
205
|
+
await stepTheCounter(t, page, 7);
|
|
206
|
+
await modifyElmCode(t, testName, page, "v4", "v5");
|
|
207
|
+
await stepTheCounter(t, page, 8);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function doBrowserApplicationTest(t, testName) {
|
|
211
|
+
const page = t.context.page;
|
|
212
|
+
await page.goto(`${t.context.serverUrl}/${testName}.html`);
|
|
213
|
+
|
|
214
|
+
const inc = "#incrementer ";
|
|
215
|
+
const dec = "#decrementer ";
|
|
216
|
+
|
|
217
|
+
await checkCodeVersion(t, page, "v1");
|
|
218
|
+
await stepTheCounter(t, page, 1, inc);
|
|
219
|
+
await stepTheCounter(t, page, 2, inc);
|
|
220
|
+
await stepTheCounter(t, page, 3, inc);
|
|
221
|
+
await clickLink(page, "#nav-decrement");
|
|
222
|
+
await stepTheCounter(t, page, 2, dec);
|
|
223
|
+
await modifyElmCode(t, testName, page, "v1", "v2");
|
|
224
|
+
await stepTheCounter(t, page, 1, dec);
|
|
225
|
+
await clickLink(page, "#nav-increment");
|
|
226
|
+
await stepTheCounter(t, page, 2, inc);
|
|
227
|
+
await modifyElmCode(t, testName, page, "v2", "v3");
|
|
228
|
+
await stepTheCounter(t, page, 3, inc);
|
|
229
|
+
await stepTheCounter(t, page, 4, inc);
|
|
230
|
+
await modifyElmCode(t, testName, page, "v3", "v4");
|
|
231
|
+
await stepTheCounter(t, page, 5, inc);
|
|
232
|
+
await modifyElmCode(t, testName, page, "v4", "v5");
|
|
233
|
+
await clickLink(page, "#nav-decrement");
|
|
234
|
+
await stepTheCounter(t, page, 4, dec);
|
|
235
|
+
await stepTheCounter(t, page, 3, dec);
|
|
236
|
+
await clickLink(page, "#nav-increment");
|
|
237
|
+
await stepTheCounter(t, page, 4, inc);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
test("if Browser.Navigation.Key cannot be found, degrade gracefully", async (t) => {
|
|
241
|
+
const testName = "BrowserApplicationMissingNavKeyError";
|
|
242
|
+
// There was a bug (https://github.com/klazuka/elm-hot/issues/15) which caused an infinite loop
|
|
243
|
+
// when the root of the model was a union type and the Browser.Navigation.Key could not be found.
|
|
244
|
+
const page = t.context.page;
|
|
245
|
+
await page.goto(`${t.context.serverUrl}/${testName}.html`);
|
|
246
|
+
// If we made it this far, then the page successfully loaded.
|
|
247
|
+
t.pass();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// TEST BUILDING BLOCKS
|
|
251
|
+
|
|
252
|
+
async function stepTheCounter(t, page, expectedPost, selectorScope = "") {
|
|
253
|
+
await incrementCounter(page, selectorScope);
|
|
254
|
+
t.is(await getCounterValue(page, selectorScope), expectedPost);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function modifyElmCode(
|
|
258
|
+
t,
|
|
259
|
+
testName,
|
|
260
|
+
page,
|
|
261
|
+
oldVersion,
|
|
262
|
+
newVersion,
|
|
263
|
+
selectorScope = "",
|
|
264
|
+
) {
|
|
265
|
+
const pathToElmCode = path.join(__dirname, `./fixtures/build/${testName}.js`);
|
|
266
|
+
const elmCode = fs.readFileSync(pathToElmCode, { encoding: "utf8" });
|
|
267
|
+
const originalFragment = `elm$html$Html$text('code: ${oldVersion}')`;
|
|
268
|
+
const modifiedFragment = `elm$html$Html$text('code: ${newVersion}')`;
|
|
269
|
+
const newElmCode = elmCode.replace(originalFragment, modifiedFragment);
|
|
270
|
+
if (newElmCode === elmCode) {
|
|
271
|
+
throw Error(
|
|
272
|
+
"Failed to modify the compiled Elm code on disk: pattern not found",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
fs.writeFileSync(pathToElmCode, newElmCode);
|
|
276
|
+
// console.log("Finished writing to the compiled Elm file on disk");
|
|
277
|
+
await page.waitForSelector(selectorScope + codeVersionId);
|
|
278
|
+
// console.log("done sleeping");
|
|
279
|
+
|
|
280
|
+
await checkCodeVersion(t, page, newVersion, selectorScope);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function clickLink(page, selector) {
|
|
284
|
+
// console.log("Clicking link at selector", selector);
|
|
285
|
+
await Promise.all([
|
|
286
|
+
page.waitForNavigation({ timeout: 5000 }),
|
|
287
|
+
page.click(selector, { delay: 10 }),
|
|
288
|
+
]);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ELM COUNTER MANIPULATION
|
|
292
|
+
|
|
293
|
+
// these must match the ids used in the Elm counter example program
|
|
294
|
+
const buttonId = "#counter-button";
|
|
295
|
+
const valueId = "#counter-value";
|
|
296
|
+
const codeVersionId = "#code-version";
|
|
297
|
+
|
|
298
|
+
async function incrementCounter(page, selectorScope = "") {
|
|
299
|
+
// console.log("Stepping the counter " + selectorScope);
|
|
300
|
+
await page.click(selectorScope + buttonId, { delay: 10 });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function getCounterValue(page, selectorScope = "") {
|
|
304
|
+
const selector =
|
|
305
|
+
selectorScope.length > 0 ? `${selectorScope} ${valueId}` : valueId;
|
|
306
|
+
const counterValue = await page.locator(selector).waitHandle();
|
|
307
|
+
const textContent = await counterValue?.evaluate((el) => el.textContent);
|
|
308
|
+
return Number.parseInt(textContent, 10);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function checkCodeVersion(t, page, expectedVersion, selectorScope = "") {
|
|
312
|
+
const selector =
|
|
313
|
+
selectorScope.length > 0
|
|
314
|
+
? `${selectorScope} ${codeVersionId}`
|
|
315
|
+
: codeVersionId;
|
|
316
|
+
const codeVersion = await page.locator(selector).waitHandle();
|
|
317
|
+
const textContent = await codeVersion?.evaluate((el) => el.textContent);
|
|
318
|
+
t.is(textContent, `code: ${expectedVersion}`);
|
|
319
|
+
return textContent;
|
|
320
|
+
}
|
package/test.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
if ! command -v elm &>/dev/null; then
|
|
4
|
+
echo "Could not find the Elm compiler on your path"
|
|
5
|
+
exit 1
|
|
6
|
+
fi
|
|
7
|
+
|
|
8
|
+
ACTUAL_ELM_VERSION=$(elm --version)
|
|
9
|
+
|
|
10
|
+
if ! echo $ACTUAL_ELM_VERSION | grep -q "0.19.[01]"; then
|
|
11
|
+
echo "Expected Elm compiler version 0.19.0 or 0.19.1, got $ACTUAL_ELM_VERSION"
|
|
12
|
+
exit 2
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
echo "Testing with Elm $ACTUAL_ELM_VERSION"
|
|
16
|
+
npx ava --timeout=6m
|