Add an end-to-end test setup for testing escaped HTML output.

This commit is contained in:
Dillon Kearns 2021-08-03 13:17:11 -07:00
parent 614fe42095
commit b3d5d65924
18 changed files with 3198 additions and 0 deletions

View File

@ -51,6 +51,8 @@ jobs:
run: npx --no-install elm-tooling install
- name: Tests
run: npm test
- name: HTML Escaping Tests
run: (cd examples/escaping && npm install && npm test)
- name: Add elm-review, elm and elm-format to path
run: npm bin >> $GITHUB_PATH
- uses: sparksp/elm-review-action@v1

9
examples/escaping/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
node_modules/
elm-stuff/
dist/
!dist/**/*.html
.cache/
.elm-pages/
functions/render/elm-pages-cli.js
my-json-data.json

View File

@ -0,0 +1,3 @@
# Escaping
This example is for testing the html-to-string functionality, which is vendored from https://github.com/ThinkAlexandria/elm-html-in-elm.

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/style.css">
<link rel="preload" href="/elm.js" as="script">
<link rel="modulepreload" href="/index.js">
<script defer="defer" src="/elm.js" type="text/javascript"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script type="module">
import userInit from"/index.js";
let prefetchedPages=[window.location.pathname],initialLocationHash=document.location.hash.replace(/^#/,"");function loadContentAndInitializeApp(){let a=window.location.pathname.replace(/(w)$/,"$1/");a.endsWith("/")||(a+="/");const b=Elm.TemplateModulesBeta.init({flags:{secrets:null,baseUrl:document.baseURI,isPrerendering:!1,isDevServer:!1,isElmDebugMode:!1,contentJson:JSON.parse(document.getElementById("__ELM_PAGES_DATA__").innerHTML),userFlags:userInit.flags()}});return b.ports.toJsPort.subscribe(()=>{loadNamedAnchor()}),b}function loadNamedAnchor(){if(""!==initialLocationHash){const a=document.querySelector(`[name=${initialLocationHash}]`);a&&a.scrollIntoView()}}function prefetchIfNeeded(a){if(a.host===window.location.host&&!prefetchedPages.includes(a.pathname)){prefetchedPages.push(a.pathname),console.log("Preloading...",a.pathname);const b=document.createElement("link");b.setAttribute("as","fetch"),b.setAttribute("rel","prefetch"),b.setAttribute("href",origin+a.pathname+"/content.json"),document.head.appendChild(b)}}const appPromise=new Promise(function(a){document.addEventListener("DOMContentLoaded",()=>{a(loadContentAndInitializeApp())})});userInit.load(appPromise),"function"==typeof connect&&connect(function(a){appPromise.then(b=>{b.ports.fromJsPort.send({contentJson:a})})});const trigger_prefetch=b=>{const c=find_anchor(b.target);c&&c.href&&c.hasAttribute("elm-pages:prefetch")&&prefetchIfNeeded(c)};let mousemove_timeout;const handle_mousemove=a=>{clearTimeout(mousemove_timeout),mousemove_timeout=setTimeout(()=>{trigger_prefetch(a)},20)};addEventListener("touchstart",trigger_prefetch),addEventListener("mousemove",handle_mousemove);function find_anchor(a){for(;a&&"A"!==a.nodeName.toUpperCase();)a=a.parentNode;return a}
</script>
<title></title>
<meta name="generator" content="elm-pages v2.1.4">
<link rel="manifest" href="/manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#ffffff">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="canonical" href="https://elm-pages.com/escaping" /> <meta name="description" content="TODO" /> <meta property="og:image" content="TODO" /> <meta property="og:image:secure_url" content="TODO" /> <meta property="og:image:alt" content="elm-pages logo" /> <meta property="og:title" content="TODO title" /> <meta property="og:url" content="https://elm-pages.com/escaping" /> <meta property="og:description" content="TODO" /> <meta property="og:site_name" content="elm-pages" /> <meta property="twitter:card" content="summary" /> <meta property="twitter:title" content="TODO title" /> <meta property="twitter:description" content="TODO" /> <meta property="twitter:image" content="TODO" /> <meta property="twitter:image:alt" content="elm-pages logo" /> <meta property="og:type" content="website" /> <link rel="icon" sizes="32x32" type="image/png" href="https://res.cloudinary.com/dillonkearns/image/upload/c_pad,w_32,h_32,q_auto,f_png/v1603234028/elm-pages/elm-pages-icon" /> <link rel="icon" sizes="16x16" type="image/png" href="https://res.cloudinary.com/dillonkearns/image/upload/c_pad,w_16,h_16,q_auto,f_png/v1603234028/elm-pages/elm-pages-icon" /> <link rel="apple-touch-icon" sizes="180x180" href="https://res.cloudinary.com/dillonkearns/image/upload/c_pad,w_180,h_180,q_auto,f_png/v1603234028/elm-pages/elm-pages-icon" /> <link rel="apple-touch-icon" sizes="192x192" href="https://res.cloudinary.com/dillonkearns/image/upload/c_pad,w_192,h_192,q_auto,f_png/v1603234028/elm-pages/elm-pages-icon" /> <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
<script id="__ELM_PAGES_DATA__" type="application/json">{"staticData":{},"is404":false,"path":"escaping"}</script>
</head>
<body>
<div data-url="" display="none"></div>
<div><style></style><label for="note"></label></div>
</body>
</html>

View File

@ -0,0 +1,8 @@
{
"name": "dmy/elm-doc-preview",
"summary": "Offline documentation previewer",
"version": "5.0.0",
"exposed-modules": [
"Page"
]
}

View File

@ -0,0 +1,8 @@
{
"tools": {
"elm": "0.19.1",
"elm-format": "0.8.4",
"elm-json": "0.2.10",
"elm-test-rs": "1.0.0"
}
}

View File

@ -0,0 +1,56 @@
{
"type": "application",
"source-directories": [
"src",
"../../src",
".elm-pages",
"../../plugins"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"MartinSStewart/elm-serialize": "1.2.5",
"avh4/elm-color": "1.0.0",
"danyx23/elm-mimetype": "4.0.1",
"dillonkearns/elm-bcp47-language-tag": "1.0.1",
"dillonkearns/elm-markdown": "6.0.1",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/regex": "1.0.0",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.3.0",
"matheus23/elm-default-tailwind-modules": "2.0.1",
"miniBill/elm-codec": "2.0.0",
"noahzgordon/elm-color-extra": "1.0.2",
"pablohirafuji/elm-syntax-highlight": "3.4.0",
"robinheghan/murmur3": "1.0.0",
"rtfeldman/elm-css": "16.1.1",
"tripokey/elm-fuzzy": "5.2.1",
"vito/elm-ansi": "10.0.1",
"zwilias/json-decode-exploration": "6.0.0"
},
"indirect": {
"bburdette/toop": "1.0.1",
"danfishgold/base64-bytes": "1.1.0",
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/parser": "1.1.0",
"elm/random": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1",
"mgold/elm-nonempty-list": "4.2.0",
"rtfeldman/elm-hex": "1.0.0"
}
},
"test-dependencies": {
"direct": {
"elm-explorations/test": "1.2.2"
},
"indirect": {}
}
}

2547
examples/escaping/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
{
"name": "elm-pages-example",
"version": "1.0.0",
"description": "Example site built with elm-pages.",
"scripts": {
"start": "elm-pages dev",
"test": "elm-pages build --debug && git diff --exit-code .",
"serve": "npm run build && http-server ./dist -a localhost -p 3000 -c-1",
"build": "elm-pages build"
},
"author": "Dillon Kearns",
"license": "BSD-3",
"devDependencies": {
"elm-oembed": "0.0.6",
"elm-pages": "file:../..",
"elm-tooling": "^1.3.0",
"http-server": "^0.11.1"
}
}

View File

@ -0,0 +1,6 @@
export default {
load: function (elmLoaded) {},
flags: function () {
return null;
},
};

View File

@ -0,0 +1,79 @@
@import url("https://rsms.me/inter/inter.css");
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap");
body {
font-family: "Inter var" !important;
}
pre.elmsh {
padding: 10px;
margin: 0;
text-align: left;
overflow: auto;
height: 100%;
width: 500px;
font-size: 14px;
font-family: "IBM Plex Mono" !important;
}
code.elmsh {
padding: 0;
}
.elmsh-line:before {
content: attr(data-elmsh-lc);
display: inline-block;
text-align: right;
width: 40px;
padding: 0 20px 0 0;
opacity: 0.3;
}
.elmsh {
color: #f8f8f2;
background: #1e1e1e;
}
.elmsh-hl {
background: #4864aa;
}
.elmsh-add {
background: #003800;
}
.elmsh-del {
background: #380000;
}
.elmsh-comm {
color: #d4d4d4;
}
.elmsh1 {
color: #74b0df;
}
.elmsh2 {
color: #ce9178;
}
.elmsh3 {
color: #ff00ff;
}
.elmsh4 {
color: #4f76ac;
}
.elmsh5 {
color: #3dc9b0;
}
.elmsh6 {
color: #74b0df;
}
.elmsh7 {
color: #ce9178;
}
.elmsh-elm-ts,
.elmsh-js-dk,
.elmsh-css-p {
font-style: italic;
color: #4f76ac;
}
.elmsh-js-ce {
font-style: italic;
color: #5bb498;
}
.elmsh-css-ar-i {
font-weight: bold;
color: #ff0000;
}

View File

@ -0,0 +1,43 @@
pre.elmsh {
padding: 10px;
margin: 0;
text-align: left;
overflow: auto;
padding: 20px !important;
}
code.elmsh {
padding: 0;
}
code {
font-family: 'Roboto Mono' !important;
font-size: 20px !important;
line-height: 28px;
}
.elmsh-line:before {
/* content: attr(data-elmsh-lc); */
display: inline-block;
text-align: right;
width: 40px;
padding: 0 20px 0 0;
opacity: 0.3;
}
.elmsh {
color: #f8f8f2;
background: #000;
}
.elmsh-hl {background: #343434;}
.elmsh-add {background: #003800;}
.elmsh-del {background: #380000;}
.elmsh-comm {color: #75715e;}
.elmsh1 {color: #ae81ff;}
.elmsh2 {color: #e6db74;}
.elmsh3 {color: #66d9ef;}
.elmsh4 {color: #f92672;}
.elmsh5 {color: #a6e22e;}
.elmsh6 {color: #ae81ff;}
.elmsh7 {color: #fd971f;}

View File

@ -0,0 +1,18 @@
module Api exposing (routes)
import ApiRoute
import DataSource exposing (DataSource)
import DataSource.Http
import Html exposing (Html)
import Json.Encode
import OptimizedDecoder as Decode
import Route exposing (Route)
import Secrets
routes :
DataSource (List Route)
-> (Html Never -> String)
-> List (ApiRoute.ApiRoute ApiRoute.Response)
routes getStaticRoutes htmlToString =
[]

View File

@ -0,0 +1,75 @@
module Page.Escaping exposing (Data, Model, Msg, page)
import DataSource exposing (DataSource)
import Head
import Head.Seo as Seo
import Html.Styled as Html
import Html.Styled.Attributes as Attr
import Page exposing (Page, PageWithState, StaticPayload)
import Pages.PageUrl exposing (PageUrl)
import Pages.Url
import Shared
import View exposing (View)
type alias Model =
()
type alias Msg =
Never
type alias RouteParams =
{}
page : Page RouteParams Data
page =
Page.single
{ head = head
, data = data
}
|> Page.buildNoState { view = view }
type alias Data =
()
data : DataSource Data
data =
DataSource.succeed ()
head :
StaticPayload Data RouteParams
-> List Head.Tag
head static =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = Pages.Url.external "TODO"
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = "TODO"
, locale = Nothing
, title = "TODO title" -- metadata.title -- TODO
}
|> Seo.website
view :
Maybe PageUrl
-> Shared.Model
-> StaticPayload Data RouteParams
-> View Msg
view maybeUrl sharedModel static =
{ title = ""
, body =
[ Html.label [ Attr.for "note" ] []
]
}

View File

@ -0,0 +1,74 @@
module Page.Index exposing (Data, Model, Msg, page)
import DataSource exposing (DataSource)
import DataSource.File
import Head
import Head.Seo as Seo
import Html.Styled exposing (text)
import Page exposing (Page, PageWithState, StaticPayload)
import Pages.PageUrl exposing (PageUrl)
import Pages.Url
import Shared
import View exposing (View)
type alias Model =
()
type alias Msg =
Never
type alias RouteParams =
{}
page : Page RouteParams Data
page =
Page.single
{ head = head
, data = data
}
|> Page.buildNoState { view = view }
type alias Data =
()
data : DataSource Data
data =
DataSource.succeed ()
head :
StaticPayload Data RouteParams
-> List Head.Tag
head static =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = Pages.Url.external "TODO"
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = "TODO"
, locale = Nothing
, title = "TODO title" -- metadata.title -- TODO
}
|> Seo.website
view :
Maybe PageUrl
-> Shared.Model
-> StaticPayload Data RouteParams
-> View Msg
view maybeUrl sharedModel static =
{ title = "Index page"
, body =
[ text "This is the index page." ]
}

View File

@ -0,0 +1,105 @@
module Shared exposing (Data, Model, Msg(..), SharedMsg(..), template)
import Browser.Navigation
import Css.Global
import DataSource
import DataSource.Http
import Html exposing (Html)
import Html.Styled
import OptimizedDecoder as D
import Pages.Flags
import Pages.PageUrl exposing (PageUrl)
import Path exposing (Path)
import Route exposing (Route)
import Secrets
import SharedTemplate exposing (SharedTemplate)
import Tailwind.Utilities
import View exposing (View)
template : SharedTemplate Msg Model Data msg
template =
{ init = init
, update = update
, view = view
, data = data
, subscriptions = subscriptions
, onPageChange = Just OnPageChange
}
type Msg
= OnPageChange
{ path : Path
, query : Maybe String
, fragment : Maybe String
}
type alias Data =
()
type SharedMsg
= NoOp
type alias Model =
{ showMobileMenu : Bool
}
init :
Maybe Browser.Navigation.Key
-> Pages.Flags.Flags
->
Maybe
{ path :
{ path : Path
, query : Maybe String
, fragment : Maybe String
}
, metadata : route
, pageUrl : Maybe PageUrl
}
-> ( Model, Cmd Msg )
init _ flags maybePagePath =
( { showMobileMenu = False }
, Cmd.none
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnPageChange _ ->
( { model | showMobileMenu = False }, Cmd.none )
subscriptions : Path -> Model -> Sub Msg
subscriptions _ _ =
Sub.none
data : DataSource.DataSource Data
data =
DataSource.succeed ()
view :
Data
->
{ path : Path
, route : Maybe Route
}
-> Model
-> (Msg -> msg)
-> View msg
-> { body : Html msg, title : String }
view stars page model toMsg pageView =
{ body =
Html.Styled.div []
pageView.body
|> Html.Styled.toUnstyled
, title = pageView.title
}

View File

@ -0,0 +1,92 @@
module Site exposing (config)
import Cloudinary
import DataSource
import Head
import MimeType
import Pages.Manifest as Manifest
import Pages.Url
import Route exposing (Route)
import SiteConfig exposing (SiteConfig)
config : SiteConfig Data
config =
{ data = data
, canonicalUrl = canonicalUrl
, manifest = manifest
, head = head
}
type alias Data =
{ siteName : String
}
data : DataSource.DataSource Data
data =
DataSource.map Data
--(StaticFile.request "site-name.txt" StaticFile.body)
(DataSource.succeed "site-name")
head : Data -> List Head.Tag
head static =
[ Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)
, Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)
, Head.appleTouchIcon (Just 180) (cloudinaryIcon MimeType.Png 180)
, Head.appleTouchIcon (Just 192) (cloudinaryIcon MimeType.Png 192)
, Head.sitemapLink "/sitemap.xml"
]
canonicalUrl : String
canonicalUrl =
"https://elm-pages.com"
manifest : Data -> Manifest.Config
manifest static =
Manifest.init
{ name = static.siteName
, description = "elm-pages - " ++ tagline
, startUrl = Route.Index |> Route.toPath
, icons =
[ icon webp 192
, icon webp 512
, icon MimeType.Png 192
, icon MimeType.Png 512
]
}
|> Manifest.withShortName "elm-pages"
tagline : String
tagline =
"A statically typed site generator"
webp : MimeType.MimeImage
webp =
MimeType.OtherImage "webp"
icon :
MimeType.MimeImage
-> Int
-> Manifest.Icon
icon format width =
{ src = cloudinaryIcon format width
, sizes = [ ( width, width ) ]
, mimeType = format |> Just
, purposes = [ Manifest.IconPurposeAny, Manifest.IconPurposeMaskable ]
}
cloudinaryIcon :
MimeType.MimeImage
-> Int
-> Pages.Url.Url
cloudinaryIcon mimeType width =
Cloudinary.urlSquare "v1603234028/elm-pages/elm-pages-icon" (Just mimeType) width

View File

@ -0,0 +1,23 @@
module View exposing (View, map, placeholder)
import Html.Styled exposing (text)
type alias View msg =
{ title : String
, body : List (Html.Styled.Html msg)
}
map : (msg1 -> msg2) -> View msg1 -> View msg2
map fn doc =
{ title = doc.title
, body = List.map (Html.Styled.map fn) doc.body
}
placeholder : String -> View msg
placeholder moduleName =
{ title = "Placeholder - " ++ moduleName
, body = [ text moduleName ]
}