Merge text.love

[?]
Jan 21, 2023, 6:30 AM
ECBDENZ4ZEIYS3FJQNTIW2NITDIQLYUFPIPA7KV4Z5TYVI5EECPAC

Dependencies

  • [2] JCXL74WV bring back everything from commit a68647ae22
  • [3] 4DHGKUMD Merge text.love
  • [4] A4BSGS2C Merge lines.love
  • [5] 2CK5QI7W make love event names consistent
  • [6] APX2PY6G stop tracking wallclock time
  • [7] OWK3U6VD tests for drawing polygons
  • [8] TO6Y2G3U more decoupling editor tests from App
  • [9] JOPVPUSA editing source code from within the app
  • [10] UTDSCN3G Merge lines.love
  • [11] RXNR3U5E Merge text.love
  • [12] GUOQRUL7 Merge lines.love
  • [13] D4B52CQ2 Merge lines.love
  • [14] ORKN6EOB Merge lines.love
  • [15] DB7HJBHJ Merge lines.love
  • [16] 66X36NZN a little more prose describing manual_tests
  • [17] ZTZOO2OQ Merge lines.love
  • [18] CE4LZV4T drop last couple of manual tests
  • [19] VHQCNMAR several more modules
  • [20] 3PSFWAIL Merge lines.love
  • [21] V7LATJC7 bugfix: resize
  • [22] T7SJSJIH test: undo naming a point
  • [23] OTIBCAUJ love2d scaffold
  • [24] CRYGI3LR more drawing tests
  • [25] ETXNVRPT Merge lines.love
  • [26] W5H5YI6S Merge text.love
  • [27] HOUCPP7P Merge text.love
  • [28] VP5KC4XZ Merge lines.love
  • [29] LNUHQOGH start passing in Editor_state explicitly
  • [30] VXORMHME delete experimental REPL
  • [31] KAUD3YIK tests: deleting points/shapes
  • [32] T4FRZSYL delete an ancient, unused file
  • [33] 2CTN2IEF Merge lines.love
  • [34] R5QXEHUI somebody stop me
  • [35] APYPFFS3 call edit rather than App callbacks in tests
  • [36] 73OCE2MC after much struggle, a brute-force undo
  • [37] TFM6F5OD Merge lines.love
  • [38] TGZAJUEF bring back a set of constants
  • [39] 36Z442IV back to commit 8123959e52f without code editing
  • [40] VZJHGWSP Merge lines.love
  • [41] VHUNJHXB Merge lines.love
  • [42] XX7G2FFJ intermingle freehand line drawings with text
  • [43] L6XA5EY2 test: moving a point
  • [44] 3QNOKBFM beginnings of a test harness
  • [45] LXTTOB33 extract a couple of files
  • [46] RSZD5A7G forgot to add json.lua
  • [47] 7DYUAOI6 test: undo moving point
  • [48] XGHCLIKB Merge lines.love
  • [49] BLWAYPKV extract a module
  • [50] LF7BWEG4 group all editor globals
  • [51] 42LVB4DE test: naming a point
  • [52] ORRSP7FV deduce test names on failures
  • [53] FS2ITYYH record a known issue
  • [54] 4AXV2HG4 all pending manual tests done!
  • [55] OGUV4HSA remove some memory leaks from rendered fragments
  • [56] TVCPXAAU rename
  • [57] 6LJZN727 handle chords
  • [58] PX7DDEMO autosave slightly less aggressively
  • [59] 2L5MEZV3 experiment: new edit namespace
  • [60] KKMFQDR4 editing source code from within the app
  • [61] UHB4GARJ left/right margin -> left/right coordinates
  • [62] K2X6G75Z start writing some tests for drawings
  • [63] CNCYMM6A make test initializations a little more obvious
  • [64] KMSL74GA support selections in the source editor
  • [65] C3GUE45I Merge text.love
  • [66] 4YDBYBA4 clean up memory leak experiments
  • [67] VSJS6O4C Merge text.love
  • [68] MD3W5IRA new fork: rip out drawing support
  • [69] KG7YVGVR Merge lines.love
  • [70] EX43CDDI Merge text.love
  • [71] 2WGHUWE6 self-documenting 0 Test_right_margin
  • [72] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [73] D2GCFTTT clean up repl functionality
  • [74] TLOAPLBJ add a license
  • [75] BULPIBEG beginnings of a module for the text editor
  • [76] AYX33NBC Merge lines.love
  • [77] AVTNUQYR basic test-enabled framework
  • [78] THRPA4VV Merge text.love

Change contents

  • file deletion: drawing_tests.lua (----------)drawing_tests.lua (----------)
    [5.2][5.1534:1575](),[5.2][5.1534:1575](),[5.1575][5.15:15]()
    check_eq(Editor_state.next_history, 3, 'next_history')
    check_eq(drawing.shapes[1].mode, 'line', 'shape:1')
    check_eq(drawing.shapes[2].mode, 'line', 'shape:2')
    -- wait until save
    check_eq(#Editor_state.lines[1].shapes, 2, 'save')
    end
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(Editor_state.next_history, 2, 'next_history')
    check_eq(p2.x, 35, 'x')
    check_eq(p2.y, 36, 'y')
    -- wait until save
    check_eq(drawing.shapes[1].mode, 'deleted', 'shape:1')
    check_eq(drawing.shapes[2].mode, 'deleted', 'shape:2')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    local drawing = Editor_state.lines[1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(#drawing.shapes, 2, 'baseline/#shapes')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    check_eq(drawing.shapes[2].mode, 'line', 'baseline/shape:2')
    -- hover on the common point and delete
    App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+36)
    edit.run_after_keychord(Editor_state, 'C-d')
    check_eq(p2.x, 35, 'save/x')
    check_eq(p2.y, 36, 'save/y')
    end
    function test_undo_delete_point()
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(Editor_state.next_history, 3, 'next_history')
    check_eq(p2.name, '', 'undo') -- not quite what it was before, but close enough
    -- wait until save
    check_eq(Editor_state.next_history, 4, 'next_history')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    edit.run_after_keychord(Editor_state, 'C-z') -- bug: need to undo twice
    local drawing = Editor_state.lines[1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(p2.x, 26, 'x')
    check_eq(p2.y, 44, 'y')
    -- exit 'move' mode
    edit.run_after_mouse_click(Editor_state, Editor_state.left+26, Editor_state.top+Drawing_padding_top+44, 1)
    check_eq(p1.x, 5, 'baseline/p1:x')
    check_eq(p1.y, 6, 'baseline/p1:y')
    check_eq(p2.x, 35, 'baseline/p2:x')
    check_eq(p2.y, 36, 'baseline/p2:y')
    check_nil(p2.name, 'baseline/p2:name')
    -- move p2
    edit.run_after_keychord(Editor_state, 'C-u')
    App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
    edit.update(Editor_state, 0.05)
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(#drawing.points, 2, 'baseline/#points')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(p2.name, '', 'save')
    end
    function test_undo_move_point()
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(drawing.shapes[1].mode, 'deleted', 'shape:1')
    check_eq(drawing.shapes[2].mode, 'deleted', 'shape:2')
    -- wait for some time
    check_eq(p2.name, 'A', 'baseline')
    check_eq(#Editor_state.history, 3, 'baseline/history:2')
    check_eq(Editor_state.next_history, 4, 'baseline/next_history')
    --? print('b', Editor_state.lines.current_drawing)
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    local drawing = Editor_state.lines[1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(p1.x, 5, 'baseline/p1:x')
    check_eq(p1.y, 6, 'baseline/p1:y')
    check_eq(p2.x, 35, 'baseline/p2:x')
    check_eq(p2.y, 36, 'baseline/p2:y')
    check_nil(p2.name, 'baseline/p2:name')
    check_eq(#Editor_state.history, 1, 'baseline/history:1')
    --? print('a', Editor_state.lines.current_drawing)
    -- enter 'name' mode without moving the mouse
    edit.run_after_keychord(Editor_state, 'C-n')
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(#drawing.points, 2, 'baseline/#points')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(drawing.shapes[1].mode, 'deleted', 'check')
    end
    function test_undo_name_point()
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(drawing.shapes[1].mode, 'polygon', 'baseline/mode')
    check_eq(#drawing.shapes[1].vertices, 3, 'baseline/vertices')
    -- hover on a point and delete
    App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
    edit.run_after_keychord(Editor_state, 'C-d')
    -- there's < 3 points left, so the whole polygon is deleted
    check_eq(drawing.shapes[1].mode, 'polygon', 'shape')
    check_eq(#drawing.shapes[1].vertices, 3, 'vertices')
    end
    function test_delete_point_from_polygon()
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(drawing.shapes[1].mode, 'polygon', 'baseline/mode')
    check_eq(#drawing.shapes[1].vertices, 4, 'baseline/vertices')
    -- hover on a point and delete
    App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)
    edit.run_after_keychord(Editor_state, 'C-d')
    -- just the one point is deleted
    check_eq(drawing.shapes[1].mode, 'deleted', 'shape:1')
    check_eq(drawing.shapes[2].mode, 'line', 'shape:2')
    end
    function test_delete_point_from_polygon()
    check_eq(#drawing.shapes, 2, 'baseline/#shapes')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    check_eq(drawing.shapes[2].mode, 'line', 'baseline/shape:2')
    -- hover on one of the lines and delete
    App.mouse_move(Editor_state.left+25, Editor_state.top+Drawing_padding_top+26)
    edit.run_after_keychord(Editor_state, 'C-d')
    -- only that line is deleted
    check_eq(#Editor_state.lines[1].shapes, 0, 'save')
    end
    function test_delete_line_under_mouse_pointer()
    Current_time = Current_time + 3.1
    -- wait for some time
    check_eq(Editor_state.current_drawing_mode, 'line', 'mode:3')
    check_eq(drawing.pending, {}, 'pending')
    -- wait until save
    check_eq(#drawing.shapes, 2, 'baseline/#shapes')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    check_eq(drawing.shapes[2].mode, 'line', 'baseline/shape:2')
    -- hover on the common point and delete
    App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+36)
    edit.run_after_keychord(Editor_state, 'C-d')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    end
    function test_delete_lines_at_point()
    check_eq(Editor_state.current_drawing_mode, 'move', 'mode:1')
    -- move point
    App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
    edit.update(Editor_state, 0.05)
    -- line is no longer manhattan
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(#drawing.points, 2, 'baseline/#points')
    check_eq(drawing.shapes[1].mode, 'manhattan', 'baseline/shape:1')
    edit.draw(Editor_state)
    -- enter 'move' mode
    edit.run_after_keychord(Editor_state, 'C-u')
    check_eq(p2.x, 26, 'save/x')
    check_eq(p2.y, 44, 'save/y')
    end
    function test_move_point_on_manhattan_line()
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(p1.x, 5, 'baseline/p1:x')
    check_eq(p1.y, 6, 'baseline/p1:y')
    check_eq(p2.x, 35, 'baseline/p2:x')
    check_eq(p2.y, 36, 'baseline/p2:y')
    -- wait until save
    check_eq(p2.x, 26, 'x')
    check_eq(p2.y, 44, 'y')
    -- exit 'move' mode
    edit.run_after_mouse_click(Editor_state, Editor_state.left+26, Editor_state.top+Drawing_padding_top+44, 1)
    check_eq(drawing.pending.mode, 'move', 'mode:2')
    check_eq(drawing.pending.target_point, p2, 'target')
    -- move point
    App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
    edit.update(Editor_state, 0.05)
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(Editor_state.current_drawing_mode, 'move', 'mode:1')
    -- point is lifted
    check_eq(p2.x, 35, 'save/x')
    check_eq(p2.y, 36, 'save/y')
    edit.draw(Editor_state)
    -- enter 'move' mode without moving the mouse
    edit.run_after_keychord(Editor_state, 'C-u')
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(#drawing.points, 2, 'baseline/#points')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(Editor_state.current_drawing_mode, 'line', 'mode:3')
    check_eq(p2.name, 'A', 'check2')
    -- wait until save
    check_eq(p2.name, 'A', 'save')
    end
    function test_move_point()
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(Editor_state.current_drawing_mode, 'name', 'mode:2')
    -- exit 'name' mode
    edit.run_after_keychord(Editor_state, 'return')
    -- exit 'name' mode
    check_eq(p2.name, 'A', 'check1')
    -- still in 'name' mode
    -- still in 'name' mode
    check_eq(Editor_state.current_drawing_mode, 'name', 'mode:1')
    edit.run_after_text_input(Editor_state, 'A')
    edit.run_after_text_input(Editor_state, 'A')
    check_eq(p1.x, 5, 'baseline/p1:x')
    check_eq(p1.y, 6, 'baseline/p1:y')
    check_eq(p2.x, 35, 'baseline/p2:x')
    check_eq(p2.y, 36, 'baseline/p2:y')
    check_nil(p2.name, 'baseline/p2:name')
    -- enter 'name' mode without moving the mouse
    edit.run_after_keychord(Editor_state, 'C-n')
    -- enter 'name' mode without moving the mouse
    check_eq(#drawing.shapes, 1, 'baseline/#shapes')
    check_eq(#drawing.points, 2, 'baseline/#points')
    check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(p1.x, 5, 'p1:x')
    check_eq(p1.y, 6, 'p1:y')
    check_eq(p2.x, 35, 'p2:x')
    check_eq(p2.y, 36, 'p2:y')
    -- wait until save
    check_eq(p.x, 5, 'p4:x')
    check_eq(p.y, 66, 'p4:y')
    end
    function test_name_point()
    check_eq(p.x, 35, 'p3:x')
    check_eq(p.y, 96, 'p3:y')
    local p = drawing.points[drawing.shapes[1].vertices[4]]
    check_eq(p.x, 65, 'p2:x')
    check_eq(p.y, 66, 'p2:y')
    local p = drawing.points[drawing.shapes[1].vertices[3]]
    check_eq(p.x, 35, 'p1:x')
    check_eq(p.y, 36, 'p1:y')
    local p = drawing.points[drawing.shapes[1].vertices[2]]
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 5, '#points') -- currently includes every point added
    check_eq(drawing.shapes[1].mode, 'square', 'shape_mode')
    check_eq(#drawing.shapes[1].vertices, 4, 'vertices')
    local p = drawing.points[drawing.shapes[1].vertices[1]]
    check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- first point
    edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    check_eq(p.x, 75, 'p2:x')
    check_eq(p.y, 76, 'p2:y')
    -- outline of rectangle is drawn based on where the mouse is, but we can't check that so far
    end
    function test_draw_square()
    check_eq(p.x, 35, 'p1:x')
    check_eq(p.y, 36, 'p1:y')
    local p = drawing.points[pending.vertices[2]]
    check_eq(pending.mode, 'rectangle', 'shape_mode')
    check_eq(#pending.vertices, 2, 'vertices')
    local p = drawing.points[pending.vertices[1]]
    check_eq(#drawing.points, 3, '#points') -- currently includes every point added
    local pending = drawing.pending
    check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- first point
    edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    check_eq(p.x, 30, 'p4:x')
    check_eq(p.y, 41, 'p4:y')
    end
    function test_draw_rectangle_intermediate()
    check_eq(p.x, 70, 'p3:x')
    check_eq(p.y, 81, 'p3:y')
    local p = drawing.points[shape.vertices[4]]
    check_eq(p.x, 75, 'p2:x')
    check_eq(p.y, 76, 'p2:y')
    local p = drawing.points[shape.vertices[3]]
    check_eq(p.x, 35, 'p1:x')
    check_eq(p.y, 36, 'p1:y')
    local p = drawing.points[shape.vertices[2]]
    check_eq(shape.mode, 'rectangle', 'shape_mode')
    check_eq(#shape.vertices, 4, 'vertices')
    local p = drawing.points[shape.vertices[1]]
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 5, '#points') -- currently includes every point added
    local shape = drawing.shapes[1]
    check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- first point
    edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    check_eq(p.x, 35, 'p3:x')
    check_eq(p.y, 26, 'p3:y')
    end
    function test_draw_rectangle()
    check_eq(p.x, 65, 'p2:x')
    check_eq(p.y, 36, 'p2:y')
    local p = drawing.points[shape.vertices[3]]
    check_eq(p.x, 5, 'p1:x')
    check_eq(p.y, 6, 'p1:y')
    local p = drawing.points[shape.vertices[2]]
    check_eq(shape.mode, 'polygon', 'shape_mode')
    check_eq(#shape.vertices, 3, 'vertices')
    local p = drawing.points[shape.vertices[1]]
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 3, 'vertices')
    local shape = drawing.shapes[1]
    check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- first point
    edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
    check_eq(center.x, 35, 'center:x')
    check_eq(center.y, 36, 'center:y')
    check_eq(arc.start_angle, 0, 'start:angle')
    check_eq(arc.end_angle, math.pi/4, 'end:angle')
    end
    function test_draw_polygon()
    check_eq(arc.radius, 30, 'radius')
    local center = drawing.points[arc.center]
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 1, '#points')
    check_eq(drawing.shapes[1].mode, 'arc', 'shape_mode')
    local arc = drawing.shapes[1]
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- draw an arc
    edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    App.mouse_move(Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36)
    check_eq(center.x, 35, 'center:x')
    check_eq(center.y, 36, 'center:y')
    end
    function test_draw_arc()
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 1, '#points')
    check_eq(drawing.shapes[1].mode, 'circle', 'shape_mode')
    check_eq(drawing.shapes[1].radius, 30, 'radius')
    local center = drawing.points[drawing.shapes[1].center]
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- draw a circle
    App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
    edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    check_eq(Editor_state.current_drawing_mode, 'line', 'drawing_mode')
    -- no change to text either because we didn't run the text_input event
    check_eq(#drawing.shapes, 0, '#shapes')
    end
    function test_keys_do_not_affect_shape_when_mouse_up()
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- start drawing a line
    edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
    -- cancel
    edit.run_after_keychord(Editor_state, 'escape')
    edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    local drawing = Editor_state.lines[1]
    check_eq(center.x, 35, 'center:x')
    check_eq(center.y, 36, 'center:y')
    end
    function test_cancel_stroke()
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 1, '#points')
    check_eq(drawing.shapes[1].mode, 'circle', 'shape_mode')
    check_eq(drawing.shapes[1].radius, 30, 'radius')
    local center = drawing.points[drawing.shapes[1].center]
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- draw a circle
    App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
    edit.run_after_keychord(Editor_state, 'C-o')
    edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36, 1)
    local drawing = Editor_state.lines[1]
    check_eq(p1.x, 5, 'p1:x')
    check_eq(p1.y, 6, 'p1:y')
    check_eq(p2.x, 35, 'p2:x')
    check_eq(p2.y, p1.y, 'p2:y')
    end
    function test_draw_circle()
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 2, '#points')
    check_eq(drawing.shapes[1].mode, 'manhattan', 'shape_mode')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- draw a line that is more horizontal than vertical
    edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+26, 1)
    local drawing = Editor_state.lines[1]
    check_eq(p1.x, 5, 'save/p1:x')
    check_eq(p1.y, 6, 'save/p1:y')
    check_eq(p2.x, 35, 'save/p2:x')
    check_eq(p2.y, 36, 'save/p2:y')
    end
    function test_draw_horizontal_line()
    check_eq(#drawing.shapes, 1, 'save/#shapes')
    check_eq(#drawing.points, 2, 'save/#points')
    check_eq(drawing.shapes[1].mode, 'line', 'save/shape:1')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    Current_time = Current_time + 3.1
    -- wait until save
    check_eq(#drawing.shapes, 1, '#shapes')
    check_eq(#drawing.points, 2, '#points')
    check_eq(drawing.shapes[1].mode, 'line', 'shape:1')
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    local p1 = drawing.points[drawing.shapes[1].p1]
    local p2 = drawing.points[drawing.shapes[1].p2]
    check_eq(#Editor_state.lines, 2, 'baseline/#lines')
    check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
    check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
    check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
    check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
    -- draw a line
    edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
    local drawing = Editor_state.lines[1]
    -- draw a line
    check_nil(App.filesystem['foo'], 'early')
    -- wait until save
    check_eq(App.filesystem['foo'], '```lines\n```\n\n', 'check')
    end
    function test_draw_line()
    Current_time = Current_time + 3.1
    -- wait until save
  • file deletion: source_text_tests.lua (----------)source_text_tests.lua (----------)
    [5.2][5.83739:83784](),[5.2][5.83739:83784](),[5.83784][5.3561:3561]()
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, '1/cursor:pos')
    end
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')
    end
    function test_search_wrap_upwards()
    check_eq(Editor_state.cursor1.line, 1, '2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, '2/cursor:pos')
    end
    function test_search_wrap()
    check_eq(Editor_state.cursor1.line, 4, '2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, '2/cursor:pos')
    end
    function test_search_upwards()
    check_eq(Editor_state.cursor1.line, 2, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')
    -- reset cursor
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- search for second occurrence
    edit.run_after_keychord(Editor_state, 'C-f')
    check_eq(Editor_state.selection1.line, 1, 'line')
    check_eq(Editor_state.selection1.pos, 2, 'pos')
    end
    check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')
    check_nil(Editor_state.selection1.line, 'baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    edit.run_after_keychord(Editor_state, 'C-z')
    -- selection is restored
    App.screen.check(y, 'xyz', 'screen:3')
    end
    App.screen.check(y, 'defg', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    --? check_eq(Editor_state.selection1.line, 2, 'selection:line')
    --? check_eq(Editor_state.selection1.pos, 4, 'selection:pos')
    y = Editor_state.top
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    edit.run_after_keychord(Editor_state, 'C-z')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'baseline/cursor:pos')
    check_nil(Editor_state.selection1.line, 'baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')
    local y = Editor_state.top
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function test_undo_delete_text()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    y = Editor_state.top
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    App.screen.check(y, 'defg', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'baseline/cursor:pos')
    check_nil(Editor_state.selection1.line, 'baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')
    local y = Editor_state.top
    check_nil(Editor_state.selection1.line, 'selection')
    end
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_eq(Editor_state.lines[1].data, 'abc', 'data:1')
    check_eq(Editor_state.lines[2].data, 'f', 'data:2')
    -- cursor remains at start of selection
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_start_of_line()
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_eq(Editor_state.lines[1].data, 'a', 'data:1')
    check_eq(Editor_state.lines[2].data, 'def', 'data:2')
    -- cursor remains at start of selection
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_end_of_line()
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_eq(Editor_state.lines[1].data, 'akl', 'data:1')
    check_eq(Editor_state.lines[2].data, 'mno', 'data:2')
    -- cursor remains at start of selection
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_multiple_lines()
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor moves to start of selection
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_selection_reverse()
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor (remains) at start of selection
    check_eq(Editor_state.lines[1].data, 'abcdef', 'check')
    end
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_backspace_past_line_boundary()
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    edit.run_after_keychord(Editor_state, 'backspace')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abcdef', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'backspace')
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 26, 'cursor:pos')
    end
    function test_backspace_can_scroll_up()
    App.screen.check(y, 'stu', 'baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    App.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.pos, 28, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'xyz', 'baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    App.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting end the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'end')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    end
    function test_end_scrolls_down_in_wrapped_line()
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting home the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'home')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_home_scrolls_up_in_wrapped_line()
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the right arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'right')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_right_arrow_scrolls_down_in_wrapped_line()
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the left arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'left')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_left_arrow_scrolls_up_in_wrapped_line()
    App.screen.check(y, 'ghij', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after typing something the line wraps and the screen scrolls down
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_typing_on_bottom_line_scrolls_down()
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'screen:2')
    end
    function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    App.screen.check(y, 'j', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 4, 'screen_top')
    check_eq(Editor_state.cursor1.line, 5, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    edit.run_after_keychord(Editor_state, 'return')
    App.screen.check(y, 'hi', 'screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    App.screen.check(y, 'g', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 4, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    edit.run_after_keychord(Editor_state, 'return')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.run_after_keychord(Editor_state, 'pageup')
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_pageup_scrolls_up_from_middle_screen_line()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.run_after_keychord(Editor_state, 'pageup')
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    -- after pageup the cursor goes to first line
    edit.run_after_keychord(Editor_state, 'pageup')
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_pageup()
    App.screen.check(y, 'abc', 'screen:2')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    -- empty first line
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'up')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 5, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    edit.run_after_keychord(Editor_state, 'up')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'up')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'up')
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    -- the screen is unchanged
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    edit.run_after_keychord(Editor_state, 'up')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    end
    function test_up_arrow_moves_cursor()
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    y = Editor_state.top
    check_eq(Editor_state.screen_top1.line, 3, 'baseline2/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'baseline2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'baseline2/cursor:pos')
    -- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
    edit.run_after_keychord(Editor_state, 'down')
    App.screen.check(y, 'ghij', 'baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    edit.run_after_keychord(Editor_state, 'pagedown')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    App.screen.check(y, 'ghij', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghij', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'down')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'down')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 4, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'down')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    -- the screen is unchanged
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    edit.run_after_keychord(Editor_state, 'down')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')
    end
    function test_down_arrow_moves_cursor()
    App.screen.check(y, 'mno ', 'screen:3')
    end
    function test_pagedown_never_moves_up()
    App.screen.check(y, 'jkl ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'baseline/screen:3')
    -- after pagedown we scroll down the very long wrapping line
    edit.run_after_keychord(Editor_state, 'pagedown')
    App.screen.check(y, 'def ', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:1')
    end
    function test_pagedown_can_start_from_middle_of_long_wrapping_line()
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor')
    y = Editor_state.top + drawing_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    -- after pagedown the screen draws the drawing up top
    -- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
    edit.run_after_keychord(Editor_state, 'pagedown')
    check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    -- initially the screen displays the first line and the drawing
    -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'screen:2')
    end
    function test_pagedown_skips_drawings()
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    edit.run_after_keychord(Editor_state, 'pagedown')
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_nil(Editor_state.selection1.line, 'check')
    end
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    end
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_select_text_repeatedly_using_mouse_and_shift()
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_select_text_using_mouse_and_shift()
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_select_text_using_mouse()
    App.screen.check(y, 'zbc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'axy', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- paste some text including a newline, check that new line is created
    App.clipboard = 'xy\nz'
    edit.run_after_keychord(Editor_state, 'C-v')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_eq(Editor_state.lines[1].data, '', 'data:1')
    check_eq(Editor_state.lines[2].data, 'abc', 'data:2')
    end
    function test_insert_from_clipboard()
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_insert_newline_at_start_of_line()
    App.screen.check(y, 'bc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'a', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return')
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fg', 'screen:3')
    end
    function test_insert_newline()
    App.screen.check(y, 'de', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 1, 'check')
    check_eq(Editor_state.lines[1].data, 'ahi', 'data')
    end
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- set up a selection starting above the currently displayed page
    Editor_state.selection1 = {line=1, pos=2}
    -- delete selection
    edit.run_after_keychord(Editor_state, 'backspace')
    -- page scrolls up
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.lines[1].data, 'xyzdef', 'check')
    end
    function test_deleting_selection_may_scroll()
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    end
    function test_paste_replaces_selection()
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selected text is deleted
    check(Editor_state.selection1.line, 'check')
    end
    function test_cut()
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selection is reset since shift key is not pressed
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'data')
    end
    function test_copy_does_not_reset_selection()
    check_eq(Editor_state.lines[1].data, 'xbc', 'check')
    end
    function test_edit_with_shift_key_deletes_selection()
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'abc', 'data')
    end
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    end
    function test_cursor_movement_without_shift_resets_selection()
    check_eq(Editor_state.cursor1.pos, 20, 'cursor')
    end
    function test_select_text()
    App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    -- click past the end of the screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    App.screen.check(y, 'am', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    App.screen.check(y, 'I’m ad', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    App.screen.check(y, 'am', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 12, 'cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 12, 'cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, '’m a', 'screen:3')
    end
    function test_click_on_wrapping_line()
    App.screen.check(y, 'am I', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mad', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ijk', 'screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    App.screen.check(y, 'e fgh', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abcd ', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    App.screen.check(y, 'ghi', 'screen:3')
    end
    App.screen.check(y, 'def ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fgh', 'screen:3')
    end
    function test_draw_word_wrapping_text()
    App.screen.check(y, 'de', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_draw_wrapping_text()
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_draw_text()
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    end
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos')
    end
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_move_past_end_of_word_on_next_line()
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_multiple_spaces_to_next_word()
    check_eq(Editor_state.cursor1.pos, 8, 'check')
    end
    function test_skip_past_tab_to_next_word()
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_to_next_word()
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 5, 'pos')
    end
    function test_move_past_end_of_word()
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_word_on_previous_line()
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_skip_multiple_spaces_to_previous_word()
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_past_tab_to_previous_word()
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_to_previous_word()
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_previous_word()
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 1, 'pos')
    end
    function test_move_to_start_of_word()
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line
    end
    function test_move_right_to_next_line()
    check_eq(Editor_state.cursor1.pos, 2, 'check')
    end
    function test_move_left_to_previous_line()
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_right()
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_press_ctrl()
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    end
    function test_insert_first_character()
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_backspace_from_start_of_final_line()
    check_eq(#Editor_state.lines, 2, '#lines')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    end
    function test_backspace_to_delete_drawing()
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    end
    function test_click_to_create_drawing()
  • file deletion: source_tests.lua (----------)source_tests.lua (----------)
    [5.2][5.150262:150302](),[5.2][5.150262:150302](),[5.150302][5.147166:147166]()
    check_eq(App.filesystem['foo'], 'abc\ndef\n', 'check')
    end
    check_eq(#Editor_state.lines, 3, '#lines')
    check_eq(Editor_state.lines[1].data, 'abc', 'lines:1')
    check_eq(Editor_state.lines[2].data, 'def', 'lines:2')
    check_eq(Editor_state.lines[3].data, 'ghi', 'lines:3')
    edit.draw(Editor_state)
    end
    function test_drop_file_saves_previous()
    check_eq(Editor_state.left, Margin_left, 'edit:left')
    check_eq(Editor_state.right, App.screen.width/2 - Margin_right, 'edit:right')
    check_eq(Log_browser_state.left, App.screen.width/2 + Margin_left, 'log:left')
    check_eq(Log_browser_state.right, App.screen.width - Margin_right, 'log:right')
    end
    function test_drop_file()
    check_eq(Log_browser_state.left, App.screen.width/2 + Margin_left, 'log:left')
    check_eq(Log_browser_state.right, App.screen.width - Margin_right, 'log:right')
    end
    function test_show_log_browser_side_resizes_both_sides_if_cannot_double_window_width()
    check_eq(Editor_state.left, Margin_left, 'edit:left')
    check_eq(Editor_state.right, old_editor_right, 'edit:right')
    -- log browser margins are adjusted
    check_eq(App.screen.width, 600, 'display:width')
    -- left side margins are unchanged
    check(Show_log_browser_side, 'check')
    end
    function test_show_log_browser_side_doubles_window_width_if_possible()
    check(not Show_log_browser_side, 'baseline')
    -- pressing ctrl+l shows log-browser side
    check_eq(App.screen.width, 200, 'width')
    check_eq(App.screen.height, 400, 'height')
    check_eq(Editor_state.left, Margin_left, 'left_margin')
    check_eq(Editor_state.right, 200-Margin_right, 'right_margin')
    check_eq(Editor_state.width, 200-Margin_left-Margin_right, 'drawing_width')
    -- TODO: how to make assertions about when App.update got past the early exit?
    check_eq(App.screen.width, 300, 'baseline/width')
    check_eq(App.screen.height, 300, 'baseline/height')
    check_eq(Editor_state.left, Test_margin_left, 'baseline/left_margin')
    check_eq(Editor_state.right, 300 - Test_margin_right, 'baseline/right_margin')
    App.resize(200, 400)
  • edit in main_tests.lua at line 5
    [5.4513][2.2214:2317](),[5.4513][2.2214:2317]()
    check_eq(Editor_state.right, 300 - Test_margin_right, 'F - test_resize_window/baseline/left_margin')
  • resurrect zombie in main_tests.lua at line 10
    [5.332][2.2318:2371](),[5.332][2.2318:2371]()
    -- ugly; resize switches to real, non-test margins
  • replacement in main_tests.lua at line 11
    [5.466][2.2372:2453](),[5.466][2.2372:2453](),[5.489][2.2454:2555](),[5.489][2.2454:2555]()
    check_eq(Editor_state.left, Margin_left, 'F - test_resize_window/left_margin')
    check_eq(Editor_state.width, 200-Margin_left-Margin_right, 'F - test_resize_window/drawing_width')
    [2.2371]
    [5.466]
    check_eq(App.screen.width, 200, 'width')
    check_eq(App.screen.height, 400, 'height')
    check_eq(Editor_state.left, Margin_left, 'left_margin')
    check_eq(Editor_state.right, 200-Margin_right, 'right_margin')
    check_eq(Editor_state.width, 200-Margin_left-Margin_right, 'drawing_width')
  • resurrect zombie in app.lua at line 127
    [5.209590][2.4092:4492](),[5.209590][2.4092:4492]()
    -- save/restore various framework globals we care about -- only on very first load
    function App.snapshot_love()
    if Love_snapshot then return end
    Love_snapshot = {}
    -- save the entire initial font; it doesn't seem reliably recreated using newFont
    Love_snapshot.initial_font = love.graphics.getFont()
    end
    function App.undo_initialize()
    love.graphics.setFont(Love_snapshot.initial_font)
    end
  • edit in app.lua at line 139
    [2.4492][5.86:86](),[5.209859][5.86:86](),[5.209731][4.35182:35201](),[5.209731][4.35182:35201](),[5.209749][4.35202:35305](),[5.209749][4.35202:35305](),[5.209859][4.35307:35307](),[5.209859][4.35307:35307]()
    Test_errors = {}
    if #Test_errors > 0 then
    error('There were test failures:\n\n'..table.concat(Test_errors))
    end
  • resolve order conflict in app.lua at line 139
    [2.4492]
    [5.5849]