mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-12-25 06:33:09 +03:00
Merge branch 'master' into nri-ui-text-without-headings
This commit is contained in:
commit
b8bdb072e4
4
.gitignore
vendored
4
.gitignore
vendored
@ -238,4 +238,6 @@ documentation.json
|
||||
# direnv config file
|
||||
.envrc
|
||||
|
||||
/public
|
||||
/public
|
||||
/tests/axe-report.log
|
||||
/tests/axe-report.json
|
7
Makefile
7
Makefile
@ -3,6 +3,13 @@ SHELL:=env PATH=${PATH} /bin/sh
|
||||
.PHONY: test
|
||||
test: node_modules
|
||||
npx elm-test
|
||||
make tests/axe-report.log
|
||||
|
||||
tests/axe-report.json: public script/run-axe.sh script/axe-puppeteer.js
|
||||
script/run-axe.sh > $@
|
||||
|
||||
tests/axe-report.log: tests/axe-report.json script/format-axe-report.sh script/axe-report.jq
|
||||
script/format-axe-report.sh $< | tee $@
|
||||
|
||||
.PHONY: checks
|
||||
checks:
|
||||
|
3
elm.json
3
elm.json
@ -3,7 +3,7 @@
|
||||
"name": "NoRedInk/noredink-ui",
|
||||
"summary": "UI Widgets we use at NRI",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "6.25.0",
|
||||
"version": "6.26.1",
|
||||
"exposed-modules": [
|
||||
"Nri.Ui.Alert.V2",
|
||||
"Nri.Ui.Alert.V3",
|
||||
@ -54,6 +54,7 @@
|
||||
"Nri.Ui.PremiumCheckbox.V3",
|
||||
"Nri.Ui.PremiumCheckbox.V4",
|
||||
"Nri.Ui.PremiumCheckbox.V5",
|
||||
"Nri.Ui.PremiumCheckbox.V6",
|
||||
"Nri.Ui.SegmentedControl.V6",
|
||||
"Nri.Ui.SegmentedControl.V7",
|
||||
"Nri.Ui.Select.V5",
|
||||
|
1822
package-lock.json
generated
1822
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -31,5 +31,9 @@
|
||||
"elm-format": "0.8.1",
|
||||
"elm-test": "0.19.0-rev6",
|
||||
"request": "^2.88.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axe-core": "^3.3.0",
|
||||
"puppeteer": "^1.19.0"
|
||||
}
|
||||
}
|
||||
|
63
script/axe-puppeteer.js
Normal file
63
script/axe-puppeteer.js
Normal file
@ -0,0 +1,63 @@
|
||||
// this script is from the example axe docs at https://github.com/dequelabs/axe-core/blob/develop/doc/examples/puppeteer/axe-puppeteer.js
|
||||
// it is licensed MPL 2.0: https://github.com/dequelabs/axe-core/blob/develop/LICENSE
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const axeCore = require('axe-core');
|
||||
const { parse: parseURL } = require('url');
|
||||
const assert = require('assert');
|
||||
|
||||
// Cheap URL validation
|
||||
const isValidURL = input => {
|
||||
const u = parseURL(input);
|
||||
return u.protocol && u.host;
|
||||
};
|
||||
|
||||
// node axe-puppeteer.js <url>
|
||||
const url = process.argv[2];
|
||||
assert(isValidURL(url), 'Invalid URL');
|
||||
|
||||
const main = async url => {
|
||||
let browser;
|
||||
let results;
|
||||
try {
|
||||
// Setup Puppeteer
|
||||
browser = await puppeteer.launch();
|
||||
|
||||
// Get new page
|
||||
const page = await browser.newPage();
|
||||
await page.goto(url);
|
||||
|
||||
// Inject and run axe-core
|
||||
const handle = await page.evaluateHandle(`
|
||||
// Inject axe source code
|
||||
${axeCore.source}
|
||||
// Run axe
|
||||
axe.run()
|
||||
`);
|
||||
|
||||
// Get the results from `axe.run()`.
|
||||
results = await handle.jsonValue();
|
||||
// Destroy the handle & return axe results.
|
||||
await handle.dispose();
|
||||
} catch (err) {
|
||||
// Ensure we close the puppeteer connection when possible
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
// Re-throw
|
||||
throw err;
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
return results;
|
||||
};
|
||||
|
||||
main(url)
|
||||
.then(results => {
|
||||
console.log(JSON.stringify(results));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error running axe-core:', err.message);
|
||||
process.exit(1);
|
||||
});
|
15
script/axe-report.jq
Normal file
15
script/axe-report.jq
Normal file
@ -0,0 +1,15 @@
|
||||
def node: " at \(.target | join(" ")):\n\n \(.failureSummary | gsub("\n"; "\n "))";
|
||||
def violation: " \(.id): \(.impact) violation with \(.nodes | length) instances.\n\n \(.help) (\(.helpUrl))\n\n\(.nodes | map(node) | join("\n\n"))";
|
||||
|
||||
"Tested \(.url) with \(.testEngine.name) \(.testEngine.version) at \(.timestamp)
|
||||
|
||||
Agent information:
|
||||
|
||||
\(.testEnvironment | to_entries | map("\(.key): \(.value)") | join("\n "))
|
||||
|
||||
Summary: \(.passes | length) passes | \(.violations | map(.nodes | length) | add) violations | \(.incomplete | map(.nodes | length) | add) incomplete | \(.inapplicable | length) inapplicable
|
||||
|
||||
Violations:
|
||||
|
||||
\(.violations | map(violation) | join("\n\n"))
|
||||
"
|
31
script/format-axe-report.sh
Executable file
31
script/format-axe-report.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
JSON_FILE="${1:-}"
|
||||
if test -z "$JSON_FILE"; then
|
||||
echo "Please specify a report JSON file as the first argument."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -r -f script/axe-report.jq "$JSON_FILE"
|
||||
|
||||
# ideally we'd fail on any failures, but we have had a bunch build up over time!
|
||||
# So right now, we need to fail if the error count is not exactly what we
|
||||
# expect. This failure reminds us to come back and ratchet down the number of
|
||||
# failures to the correct value.
|
||||
NUM_ERRORS="$(jq '.violations | map(.nodes | length) | add' "$JSON_FILE")"
|
||||
TARGET_ERRORS=155
|
||||
if test "$NUM_ERRORS" -ne "$TARGET_ERRORS"; then
|
||||
echo "got $NUM_ERRORS errors, but expected $TARGET_ERRORS."
|
||||
echo
|
||||
echo 'If it went down, hooray!'
|
||||
echo "Check out ${0:-} and change the count to the reported value above."
|
||||
echo
|
||||
echo "If it went up, let's fix it instead."
|
||||
echo "Since there are so many errors right now, a decent debugging strategy is:"
|
||||
echo
|
||||
echo " 1. save tests/axe-report.log somewhere ('mv tests/axe-report.log tests/axe-report.log.failing' is one way)"
|
||||
echo " 2. undo your changes ('git stash' or 'checkout master')"
|
||||
echo " 3. regenerate the log with 'make tests/axe-report.log'"
|
||||
echo " 4. compare the output with 'diff -u tests/axe-report.log tests/axe-report.log.failing'"
|
||||
fi
|
12
script/run-axe.sh
Executable file
12
script/run-axe.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# start a web server in the background and tear it down when exiting
|
||||
./script/serve.sh public &
|
||||
SERVER_PID=$!
|
||||
cleanup() {
|
||||
kill "$SERVER_PID"
|
||||
}
|
||||
trap cleanup EXIT INT
|
||||
|
||||
node script/axe-puppeteer.js http://localhost:8000
|
115
src/Nri/Ui/PremiumCheckbox/V6.elm
Normal file
115
src/Nri/Ui/PremiumCheckbox/V6.elm
Normal file
@ -0,0 +1,115 @@
|
||||
module Nri.Ui.PremiumCheckbox.V6 exposing (view)
|
||||
|
||||
{-|
|
||||
|
||||
@docs view
|
||||
|
||||
This module is used when there may or may not be Premium
|
||||
content to be "checked"!
|
||||
|
||||
|
||||
# Changes from V5
|
||||
|
||||
- Allow checkbox to show pennant, or not, based on bool
|
||||
- Remove PremiumWithWriting, it's only Premium now
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled as Html exposing (Html)
|
||||
import Css exposing (..)
|
||||
import Html.Styled exposing (fromUnstyled)
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Nri.Ui.Checkbox.V5 as Checkbox
|
||||
import Svg exposing (..)
|
||||
import Svg.Attributes exposing (..)
|
||||
|
||||
|
||||
{-| A checkbox that should be used for premium content
|
||||
|
||||
- `onChange`: A message for when the user toggles the checkbox
|
||||
- `onLockedClick`: A message for when the user clicks a checkbox they don't have PremiumLevel for.
|
||||
If you get this message, you should show an `Nri.Ui.Premium.Model.view`
|
||||
|
||||
-}
|
||||
view :
|
||||
{ label : String
|
||||
, id : String
|
||||
, selected : Checkbox.IsSelected
|
||||
, disabled : Bool
|
||||
, isLocked : Bool
|
||||
, isPremium : Bool
|
||||
, onChange : Bool -> msg
|
||||
, onLockedClick : msg
|
||||
}
|
||||
-> Html msg
|
||||
view config =
|
||||
Html.div
|
||||
[ css
|
||||
[ displayFlex
|
||||
, alignItems center
|
||||
]
|
||||
]
|
||||
[ Checkbox.viewWithLabel
|
||||
{ identifier = config.id
|
||||
, label = config.label
|
||||
, setterMsg =
|
||||
if config.isLocked then
|
||||
\_ -> config.onLockedClick
|
||||
|
||||
else
|
||||
config.onChange
|
||||
, selected = config.selected
|
||||
, disabled = config.disabled
|
||||
, theme =
|
||||
if config.isLocked then
|
||||
Checkbox.Locked
|
||||
|
||||
else
|
||||
Checkbox.Square
|
||||
}
|
||||
, if config.isPremium then
|
||||
premiumFlag
|
||||
|
||||
else
|
||||
Html.text ""
|
||||
]
|
||||
|
||||
|
||||
premiumFlag : Html msg
|
||||
premiumFlag =
|
||||
svg
|
||||
[ version "1.1"
|
||||
, id "Layer_1"
|
||||
, Svg.Attributes.width "25"
|
||||
, Svg.Attributes.height "19"
|
||||
, Svg.Attributes.viewBox "0 0 25 19"
|
||||
, Svg.Attributes.style "margin-left: 8px;"
|
||||
]
|
||||
[ Svg.title [] [ text "Premium" ]
|
||||
, Svg.style [] [ text " .premium-flag-st0{fill:#FEC709;} .premium-flag-st1{fill:#146AFF;} " ]
|
||||
, g [ id "Page-1" ]
|
||||
[ g
|
||||
[ id "icon_x2F_p-mini-pennant-yellow"
|
||||
, Svg.Attributes.transform "translate(0.000000, -3.000000)"
|
||||
]
|
||||
[ g
|
||||
[ id "Group"
|
||||
, Svg.Attributes.transform "translate(0.000000, 3.750000)"
|
||||
]
|
||||
[ polygon
|
||||
[ id "Fill-2"
|
||||
, class "premium-flag-st0"
|
||||
, points "12.7,0 0,0 0,13.8 0,15.8 0,17.5 7.3,17.5 24.8,17.5 19.4,8.1 24.8,0 "
|
||||
]
|
||||
[]
|
||||
, Svg.path
|
||||
[ id "P"
|
||||
, class "premium-flag-st1"
|
||||
, d "M7.5,3.8h4.2c1.1,0,1.9,0.3,2.5,0.8s0.9,1.2,0.9,2.1s-0.3,1.6-0.9,2.1c-0.6,0.5-1.4,0.8-2.5,0.8H9.3 v4.1H7.5V3.8z M11.5,8.1c0.6,0,1.1-0.1,1.4-0.4c0.3-0.3,0.5-0.6,0.5-1.1c0-0.5-0.2-0.9-0.5-1.1c-0.3-0.3-0.8-0.4-1.4-0.4H9.3v3 H11.5z"
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|> fromUnstyled
|
@ -12,7 +12,7 @@ import Html.Styled.Attributes exposing (css)
|
||||
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample)
|
||||
import Nri.Ui.Checkbox.V5 as Checkbox
|
||||
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel(..))
|
||||
import Nri.Ui.PremiumCheckbox.V5 as PremiumCheckbox
|
||||
import Nri.Ui.PremiumCheckbox.V6 as PremiumCheckbox
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
@ -76,13 +76,6 @@ update msg state =
|
||||
-- INTERNAL
|
||||
|
||||
|
||||
type alias PremiumExampleConfig =
|
||||
{ disabled : Bool
|
||||
, teacherPremiumLevel : PremiumLevel
|
||||
, pennant : PremiumCheckbox.Pennant
|
||||
}
|
||||
|
||||
|
||||
viewInteractableCheckbox : Id -> State -> Html Msg
|
||||
viewInteractableCheckbox id state =
|
||||
Checkbox.viewWithLabel
|
||||
@ -186,7 +179,7 @@ viewPremiumCheckboxes state =
|
||||
Checkbox.NotSelected
|
||||
, disabled = config.disabled
|
||||
, isLocked = config.isLocked
|
||||
, pennant = config.pennant
|
||||
, isPremium = config.isPremium
|
||||
, onChange = ToggleCheck config.label
|
||||
, onLockedClick = NoOp
|
||||
}
|
||||
@ -196,19 +189,19 @@ viewPremiumCheckboxes state =
|
||||
{ label = "Identify Adjectives 2 (Premium)"
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, pennant = PremiumCheckbox.Premium
|
||||
, isPremium = True
|
||||
}
|
||||
, checkbox
|
||||
{ label = "Revising Wordy Phrases 1 (Writing)"
|
||||
{ label = "Identify Adjectives 2 (Free)"
|
||||
, disabled = False
|
||||
, isLocked = True
|
||||
, pennant = PremiumCheckbox.PremiumWithWriting
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
, checkbox
|
||||
{ label = "Revising Wordy Phrases 2 (Writing) (Disabled)"
|
||||
{ label = "Revising Wordy Phrases 2 (Premium, Disabled)"
|
||||
, disabled = True
|
||||
, isLocked = True
|
||||
, pennant = PremiumCheckbox.PremiumWithWriting
|
||||
, isPremium = True
|
||||
}
|
||||
]
|
||||
|
||||
|
115
tests/Spec/Nri/Ui/PremiumCheckbox/V6.elm
Normal file
115
tests/Spec/Nri/Ui/PremiumCheckbox/V6.elm
Normal file
@ -0,0 +1,115 @@
|
||||
module Spec.Nri.Ui.PremiumCheckbox.V6 exposing (spec)
|
||||
|
||||
import Html.Attributes as Attributes
|
||||
import Html.Styled
|
||||
import Nri.Ui.Checkbox.V5 as Checkbox exposing (IsSelected(..))
|
||||
import Nri.Ui.PremiumCheckbox.V6 as PremiumCheckbox
|
||||
import Test exposing (..)
|
||||
import Test.Html.Event as Event
|
||||
import Test.Html.Query as Query
|
||||
import Test.Html.Selector as Selector
|
||||
|
||||
|
||||
type Msg
|
||||
= OnLocked
|
||||
| OnChange Bool
|
||||
|
||||
|
||||
premiumView config =
|
||||
PremiumCheckbox.view
|
||||
{ label = "i am label"
|
||||
, id = "id"
|
||||
, selected = config.selected
|
||||
, disabled = config.disabled
|
||||
, isLocked = config.isLocked
|
||||
, isPremium = config.isPremium
|
||||
, onChange = OnChange
|
||||
, onLockedClick = OnLocked
|
||||
}
|
||||
|> Html.Styled.toUnstyled
|
||||
|> Query.fromHtml
|
||||
|
||||
|
||||
spec : Test
|
||||
spec =
|
||||
describe "Nri.Ui.PremiumCheckbox.V6"
|
||||
[ describe "premium"
|
||||
[ test "displays the label" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.has [ Selector.text "i am label" ]
|
||||
, test "appears selected when Selected is passed in" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.has [ Selector.attribute (Attributes.checked True) ]
|
||||
, test "appears unselected when NotSelected is passed in" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = NotSelected
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.has [ Selector.attribute (Attributes.checked False) ]
|
||||
, test "triggers onLockedClick when isLocked = True" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = False
|
||||
, isLocked = True
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.find [ Selector.tag "input" ]
|
||||
|> Event.simulate (Event.check False)
|
||||
|> Event.expect OnLocked
|
||||
, test "triggers onChange when isLocked = False" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.find [ Selector.tag "input" ]
|
||||
|> Event.simulate (Event.check False)
|
||||
|> Event.expect (OnChange False)
|
||||
, test "appears with P flag when Premium pennant is passed in" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, isPremium = True
|
||||
}
|
||||
|> Query.find [ Selector.tag "title" ]
|
||||
|> Query.has [ Selector.text "Premium" ]
|
||||
, test "is not disabled when disabled = False" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = False
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.has [ Selector.disabled False ]
|
||||
, test "is disabled when disabled = True" <|
|
||||
\() ->
|
||||
premiumView
|
||||
{ selected = Selected
|
||||
, disabled = True
|
||||
, isLocked = False
|
||||
, isPremium = False
|
||||
}
|
||||
|> Query.has [ Selector.disabled True ]
|
||||
]
|
||||
]
|
Loading…
Reference in New Issue
Block a user