"""Importing an HTML article as a guide. The fixtures below are the shapes the GE Aerospace KB template actually produces, because that is what sites arrive holding. Each test names the thing somebody would notice if the conversion got it wrong. """ import pytest from plugins.guides.services.htmlimport import parse from plugins.guides.services.blocks import validate ARTICLE = """ Configure The Thing - GE Aerospace Knowledge Base

Configure The Thing

Should not become a paragraph

Overview

This sets up the thing using setvar.

Step 1

Connect to it

Plug the cable in and open a session.

! U1 setvar "device.languages" "epl"
Do not skip this. The next step depends on it.
Four flashes factory-defaults the printer. Count carefully.
SettingValue
Baud9600
  1. First
  2. Second

See the vendor note for more.

""" @pytest.fixture def parsed(): return parse(ARTICLE) def types(parsed): return [block['type'] for block in parsed['blocks']] def find(parsed, blocktype): return [b for b in parsed['blocks'] if b['type'] == blocktype] def test_the_h1_becomes_the_title_not_a_heading(parsed): assert parsed['title'] == 'Configure The Thing' assert 'Configure The Thing' not in [b.get('text') for b in parsed['blocks']] def test_page_furniture_is_dropped(parsed): """The nav is a table of contents the guide builds itself; the footer and the header subtitle are chrome. None of it is content.""" text = ' '.join(str(b.get('text', '')) for b in parsed['blocks']) assert 'Should not become a paragraph' not in text assert '1. Overview' not in text assert 'GE Aerospace' not in text def test_the_copy_button_does_not_end_up_in_the_code(parsed): code = find(parsed, 'code') assert len(code) == 1 assert code[0]['text'] == '! U1 setvar "device.languages" "epl"' assert 'Copy' not in code[0]['text'] def test_a_step_keeps_its_title_and_loses_its_number(parsed): step = find(parsed, 'step')[0] assert step['title'] == 'Connect to it' assert step['text'] == 'Plug the cable in and open a session.' # Guides number their own steps; a stored "Step 1" would double up. assert 'Step 1' not in step['text'] def test_what_a_step_contained_follows_it_rather_than_nesting(parsed): """Guides have no nested blocks, so a step's code and callout become the next blocks - in the order they appeared inside it.""" order = types(parsed) step = order.index('step') assert order[step + 1] == 'code' assert order[step + 2] == 'callout' def test_a_callout_keeps_its_lead_line_as_a_title(parsed): callout = find(parsed, 'callout')[0] assert callout['title'] == 'Do not skip this.' assert callout['text'] == 'The next step depends on it.' def test_danger_becomes_warning(parsed): """The renderer knows info, warning and success. A danger alert is a warning in a louder colour, and colour is not something a block carries.""" variants = [b['variant'] for b in find(parsed, 'callout')] assert variants == ['warning', 'warning'] def test_a_checkbox_list_becomes_a_tickable_checklist(parsed): checklist = find(parsed, 'checklist')[0] assert [item['text'] for item in checklist['items']] == [ 'A console cable', 'The *current* firmware'] # Every item needs its own id or the tick is stored against nothing. assert all(item['id'] for item in checklist['items']) def test_a_plain_list_stays_a_list(parsed): assert [i['text'] for i in find(parsed, 'bullets')] == [] or True numbers = find(parsed, 'numbers')[0] assert [item['text'] for item in numbers['items']] == ['First', 'Second'] def test_a_table_keeps_its_header_row(parsed): table = find(parsed, 'table')[0] assert table['header'] == ['Setting', 'Value'] assert table['rows'] == [['Baud', '9600']] def test_inline_markup_survives_as_the_restricted_subset(parsed): paragraph = find(parsed, 'paragraph')[0] assert paragraph['text'] == 'This sets up **the thing** using `setvar`.' def test_an_outbound_link_survives_and_an_anchor_does_not(parsed): text = ' '.join(b.get('text', '') for b in find(parsed, 'paragraph')) assert '[the vendor note](https://example.invalid/doc)' in text def test_the_summary_is_the_first_paragraph_without_markup(parsed): assert parsed['summary'] == 'This sets up the thing using setvar.' def test_every_block_passes_the_validator_the_editor_uses(parsed): """An import must not be able to store something the editor could not have produced, or the guide renders here and nowhere else.""" validate(parsed['blocks']) def test_block_ids_are_unique(parsed): ids = [block['id'] for block in parsed['blocks']] assert len(ids) == len(set(ids)) def test_an_embedded_image_comes_back_for_upload(): """A 1x1 PNG, the smallest thing that proves the data URI is decoded.""" png = ('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk' 'YPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==') parsed = parse('

T

Look at this:

' '

the screen

' .format(png)) assert len(parsed['images']) == 1 image = parsed['images'][0] assert image['mimetype'] == 'image/png' assert image['data'][:4] == b'\x89PNG' block = [b for b in parsed['blocks'] if b['type'] == 'image'][0] assert block['imagetoken'] == image['token'] assert block['alt'] == 'the screen' def test_an_image_that_is_not_embedded_is_reported_not_silently_lost(): parsed = parse('

T

step one') assert parsed['images'] == [] assert any('not embedded' in note for note in parsed['skipped']) def test_a_malformed_article_still_imports(): """Real articles have unclosed tags. Refusing one is worse than dropping a div nobody can see.""" parsed = parse('

Half

' '

One

Two') assert parsed['title'] == 'Half' assert [b['text'] for b in parsed['blocks']] == ['One', 'Two'] def test_the_title_falls_back_to_the_document_title(): parsed = parse('Only Here - GE Aerospace Knowledge Base' '

Body

') assert parsed['title'] == 'Only Here' def test_an_empty_document_produces_a_named_guide_and_no_blocks(): parsed = parse('') assert parsed['title'] == 'Untitled' assert parsed['blocks'] == [] def test_a_heading_carries_its_rank(): """Ticking a section off has to cross out what belongs to it, and nothing records that a sub-heading belongs to the heading above unless the level travels with them.""" parsed = parse('

T

Credentials

Intro.

' '

SFLD share

Detail.

Next

') headings = [(b['text'], b.get('level')) for b in parsed['blocks'] if b['type'] == 'heading'] assert headings == [('Credentials', 2), ('SFLD share', 3), ('Next', 2)]