100 lines
5.2 KiB
Haskell
100 lines
5.2 KiB
Haskell
module Roux.SchemaOrgSpec (spec) where
|
|
|
|
import Data.Aeson (Value, decodeStrict)
|
|
import Data.CookLang (Duration (..), Metadata (..), Recipe (..))
|
|
import Data.List.NonEmpty (toList)
|
|
import Data.Maybe (fromJust, isJust)
|
|
import Data.Text (Text)
|
|
import qualified Data.Text as T
|
|
import qualified Data.Text.Encoding as TE
|
|
import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe, shouldSatisfy)
|
|
|
|
import Roux.SchemaOrg
|
|
|
|
-- | Extract the first schema.org Recipe JSON-LD from HTML.
|
|
tryDecode :: Text -> Maybe Value
|
|
tryDecode html =
|
|
case T.splitOn "<script type=\"application/ld+json" html of
|
|
(_before : rest : _) ->
|
|
-- Skip past the closing > of the opening script tag, which may have extra attributes
|
|
let afterOpenTag = T.drop 1 (T.dropWhile (/= '>') rest)
|
|
in case T.splitOn "</script>" afterOpenTag of
|
|
(jsonContent : _) -> decodeStrict (TE.encodeUtf8 (T.strip jsonContent))
|
|
_ -> Nothing
|
|
_ -> Nothing
|
|
|
|
spec :: Spec
|
|
spec = describe "SchemaOrg" $ do
|
|
describe "parseSchemaOrgRecipe" $ do
|
|
it "parses fried-rice.html embedded JSON-LD" $ do
|
|
html <- readFile "test-data/fried-rice.html"
|
|
let decoded = tryDecode (T.pack html)
|
|
decoded `shouldSatisfy` isJust
|
|
let recipe = parseSchemaOrgRecipe (fromJust decoded)
|
|
recipe `shouldSatisfy` isJust
|
|
case recipe of
|
|
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
|
|
Just r -> do
|
|
soName r `shouldBe` "Fried Rice"
|
|
soRecipeYield r `shouldBe` Just "4 to 6 servings"
|
|
soTotalTime r `shouldBe` Just "PT20M"
|
|
soRecipeCuisine r `shouldBe` Just "asian"
|
|
length (soRecipeIngredient r) `shouldBe` 14
|
|
length (soRecipeInstructions r) `shouldBe` 4
|
|
|
|
it "parses carrot-risotto.html embedded JSON-LD" $ do
|
|
html <- readFile "test-data/carrot-risotto.html"
|
|
let decoded = tryDecode (T.pack html)
|
|
decoded `shouldSatisfy` isJust
|
|
let recipe = parseSchemaOrgRecipe (fromJust decoded)
|
|
recipe `shouldSatisfy` isJust
|
|
case recipe of
|
|
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
|
|
Just r -> soName r `shouldBe` "Carrot Risotto With Chile Crisp"
|
|
|
|
describe "parseISODuration" $ do
|
|
let chk :: Text -> Maybe Duration -> IO ()
|
|
chk input expected = parseISODuration input `shouldBe` expected
|
|
|
|
it "PT20M -> 20 minutes" $ chk "PT20M" (Just (Duration 20 (Just "minutes")))
|
|
it "PT1H -> 60 minutes" $ chk "PT1H" (Just (Duration 60 (Just "minutes")))
|
|
it "PT1H30M -> 90 minutes" $ chk "PT1H30M" (Just (Duration 90 (Just "minutes")))
|
|
it "P1D -> 1440 minutes" $ chk "P1D" (Just (Duration 1440 (Just "minutes")))
|
|
it "P1DT2H -> 1560 minutes" $ chk "P1DT2H" (Just (Duration 1560 (Just "minutes")))
|
|
it "PT30M -> 30 minutes" $ chk "PT30M" (Just (Duration 30 (Just "minutes")))
|
|
it "empty string -> Nothing" $ chk "" Nothing
|
|
it "human-readable -> Nothing" $ chk "20 minutes" Nothing
|
|
|
|
describe "schemaOrgToCooklang" $ do
|
|
it "converts fried-rice schema.org recipe to Cooklang" $ do
|
|
html <- readFile "test-data/fried-rice.html"
|
|
let decoded = tryDecode (T.pack html)
|
|
recipe = parseSchemaOrgRecipe (fromJust decoded)
|
|
case recipe of
|
|
Just r -> case schemaOrgToCooklang r of
|
|
Right cook -> do
|
|
metaTitle (recipeMetadata cook) `shouldBe` Just "Fried Rice"
|
|
metaSource (recipeMetadata cook) `shouldBe` Just "https://cooking.nytimes.com/recipes/12177-fried-rice"
|
|
metaCourse (recipeMetadata cook) `shouldBe` Just "one pot, side dish"
|
|
metaCuisine (recipeMetadata cook) `shouldBe` Just "asian"
|
|
metaTotalTime (recipeMetadata cook) `shouldBe` Just (Duration 20 (Just "minutes"))
|
|
metaTags (recipeMetadata cook) `shouldBe` ["egg", "rice", "vegetarian"]
|
|
metaDescription (recipeMetadata cook) `shouldSatisfy` isJust
|
|
let sections = toList (recipeSections cook)
|
|
length sections `shouldBe` 2
|
|
Left err -> fail err
|
|
Nothing -> fail "Failed to parse schema.org recipe"
|
|
|
|
it "converts carrot-risotto schema.org recipe to Cooklang" $ do
|
|
html <- readFile "test-data/carrot-risotto.html"
|
|
let decoded = tryDecode (T.pack html)
|
|
recipe = parseSchemaOrgRecipe (fromJust decoded)
|
|
case recipe of
|
|
Just r -> case schemaOrgToCooklang r of
|
|
Right cook -> do
|
|
metaTitle (recipeMetadata cook) `shouldBe` Just "Carrot Risotto With Chile Crisp"
|
|
metaAuthor (recipeMetadata cook) `shouldBe` Just "Alexa Weibel"
|
|
metaTags (recipeMetadata cook) `shouldSatisfy` (not . null)
|
|
Left err -> fail err
|
|
Nothing -> fail "Failed to parse schema.org recipe"
|