Files
roux/test/Roux/SchemaOrgSpec.hs
T

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"