Merge branch 'master' into nri-ui-text-without-headings

This commit is contained in:
Brian Hicks 2019-07-25 08:13:26 -05:00 committed by GitHub
commit b8bdb072e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1324 additions and 890 deletions

4
.gitignore vendored
View File

@ -238,4 +238,6 @@ documentation.json
# direnv config file
.envrc
/public
/public
/tests/axe-report.log
/tests/axe-report.json

View File

@ -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:

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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
View 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
View 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

View 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

View File

@ -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
}
]

View 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 ]
]
]