diff --git a/src/main/html/Integration.html b/src/main/html/Integration.html new file mode 100644 index 0000000..0dc0c1c --- /dev/null +++ b/src/main/html/Integration.html @@ -0,0 +1,112 @@ + + + + Integration + + + + + + +

Inline editor

+
+
+
+ + +

All editors

+ +
+ +
+ + + \ No newline at end of file diff --git a/src/main/html/Part1Ex1.html b/src/main/html/Part1Ex1.html index d359632..e8774c6 100644 --- a/src/main/html/Part1Ex1.html +++ b/src/main/html/Part1Ex1.html @@ -9,7 +9,7 @@ TODO Head over to https://www.tiny.cloud/ and sign up for an API key. Replace the 'no-api-key' with your api key in the below script tag. --> - + @@ -30,9 +30,11 @@ Since a selector can match multiple elements, you can use tinymce.init to create multiple editors. TODO In the below script tag, instantiate TinyMCE editors in the below --> - - - + + + - +

Custom Editor

+ + diff --git a/src/main/ts/Part2Ex1.ts b/src/main/ts/Part2Ex1.ts index b83350e..e030270 100644 --- a/src/main/ts/Part2Ex1.ts +++ b/src/main/ts/Part2Ex1.ts @@ -16,6 +16,10 @@ Let's model the x,y of the top-left and bottom-right corners. */ export interface Boundz { // TODO: add fields: x1, y1, x2, y2 + readonly x1: number; + readonly y1: number; + readonly x2: number; + readonly y2: number; } /* @@ -27,10 +31,7 @@ We tell tsc to transpile to ES5, so IE works. Notice also that we have an explicit return type. This lets the compiler check that our code matches the type signature. */ -export const width = (b: Boundz): number => - /* TODO */ -1; - -// TODO implement height function +export const width = (b: Boundz): number => b.x2 - b.x1; /* 3. Compiling. @@ -47,3 +48,5 @@ Ok, so we started off pretty easy. Now, code is useless without tests, so let's head over to Exercise1CodeStyleTest.ts and write some tests. */ + +export const height = (b: Boundz): number => b.y2 - b.y1; diff --git a/src/main/ts/Part2Ex2ArrayFunctions.ts b/src/main/ts/Part2Ex2ArrayFunctions.ts index 36fa837..85ea027 100644 --- a/src/main/ts/Part2Ex2ArrayFunctions.ts +++ b/src/main/ts/Part2Ex2ArrayFunctions.ts @@ -10,7 +10,7 @@ We don't write loops if we can help it. Instead, we go up a level, and call func The simplest of these is 'each' which just iterates. TODO: Run the following code using this command: -yarn bedrock-auto -b chrome-headless -f src/test/ts/Exercise2ArrayFunctionsTest.ts +yarn bedrock-auto -b chrome-headless -f src/test/ts/part2/Exercise2ArrayFunctionsTest.ts */ export const runEach1 = (): void => { @@ -41,6 +41,7 @@ export const myFrogs: Frog[] = [ export const runEach2 = (): void => { // TODO: Use Arr.each and console.log to print the name of each frog + Arr.each(myFrogs, (f) => console.log(f.name)) }; /* @@ -65,8 +66,7 @@ export const runMap2 = (xs: number[]): string[] => // TODO: Return the frog's names and check it by running // yarn bedrock-auto -b chrome-headless -f src/test/ts/Exercise2ArrayFunctionsTest.ts -export const frogNames = (fs: Frog[]): string[] => - []; +export const frogNames = (fs: Frog[]): string[] => Arr.map(fs, (f) => f.name); // TODO: Return the frog's ages // TODO: Write a test for this in Exercise2ArrayFunctionsTest @@ -83,12 +83,10 @@ export const evens = (xs: number[]): number[] => // TODO: Write a function that returns all the frogs that ribbit // TODO: Run the provided test to check your answer. -export const ribbitting = (frogs: Frog[]): Frog[] => - []; +export const ribbitting = (frogs: Frog[]): Frog[] => Arr.filter(frogs, (f) => f.ribbits); // TODO: Write a function that returns all frogs aged 8 or older -export const olderFrogs = (frogs: Frog[]): Frog[] => - []; +export const olderFrogs = (frogs: Frog[]): Frog[] => Arr.filter(frogs, (f) => f.age >= 8); /* 5. Arr.exists @@ -97,8 +95,9 @@ Arr.exists returns true if there is one or more element that matches a predicate */ // TODO: Write a function that returns true if there's one or more ribbiting frogs - +export const oneOrMoreRibbitingFrogs = (frogs: Frog[]) => Arr.exists(frogs, (f: Frog) => f.ribbits === true); // TODO: Write a function that takes an array of numbers, and returns true if there are any negative numbers +export const hasNegativeNumber = (items: number[]) => Arr.exists(items, (i: number) => i < 0); /* 6. Arr.bind @@ -110,8 +109,7 @@ This behaviour of running map then flatten is why this function is sometimes cal TODO: Write a function that takes a list of strings, each string containing a comma-separated list of values, and returns all of the values as an array. */ -export const splitCsvs = (csvs: string[]): string[] => - []; +export const splitCsvs = (csvs: string[]): string[] => Arr.bind(csvs, (s) => s.split(',')); /* 7. Arr.find diff --git a/src/main/ts/Part2Ex3Optional.ts b/src/main/ts/Part2Ex3Optional.ts index 1ca39ff..d52e465 100644 --- a/src/main/ts/Part2Ex3Optional.ts +++ b/src/main/ts/Part2Ex3Optional.ts @@ -35,13 +35,15 @@ export const toPositiveInteger = (n: number): Optional => n > 0 ? Optional.some(n) : Optional.none(); // TODO: create a function which takes a string and returns some if the string is non-empty +export const isString = (s: string): Optional => s.length > 0 ? Optional.some(s) : Optional.none(); // TODO: create a function which takes a url as a string and returns the protocol part as an Optional. // The string may or may not actually have a protocol. For the protocol to be valid, it needs to be all alpha characters. // You can use a regex. // Have a look at Exercise3OptionTest.ts for example input. Make sure the tests pass. export const getProtocol = (url: string): Optional => { - throw new Error("TODO"); + let regexMatch = url.match(/^http(s)?:\/\//); + return regexMatch !== null ? Optional.some(regexMatch[0].replace('://', '')) : Optional.none(); }; /* @@ -57,10 +59,13 @@ TODO: use Optional.from to implement the following DOM function */ export const getNextSibling = (e: Element): Optional => { - throw new Error("TODO"); + return Optional.from(e.nextSibling); }; // TODO: use Optional.from to implement a similar wrapper for Element.getAttributeNode(string) +export const getElementAttribute = (e: Element, attributeName: string): Optional => { + return Optional.from(e.getAttributeNode(attributeName)); +} /* How do we get data out of an Optional? Well, that's a bit tricky since there isn't always @@ -80,11 +85,10 @@ export const message = (e: Optional): string => ); // TODO: Implement a function using fold, that takes an Optional. If it's some, double it. If it's none, return 0; +export const doubleNumber = (n: Optional): number => n.fold(() => 0, (n) => n * 2); // TODO: Implement a function that takes an Optional for any type T. Return true if it's some, and false if it's none. -const trueIfSome = (x: Optional): boolean => { - throw new Error("TODO"); -}; +const trueIfSome = (x: Optional): boolean => x.fold(() => false, () => true); /* The last function you implemented is already part of the Optional type, and is called isSome(). @@ -107,8 +111,10 @@ You can do this with fold, but getOr is a shortcut. */ // TODO: Using getOr, take an Optional<{age: number}> and turn it into an {age: number}, using a default value of 0. +export const toValueOr = (input: Optional<{age: number}>): {age: number} => input.getOr({age: 0}); // TODO: Write the same function using fold +export const toValueOrWithFold = (input: Optional<{age: number}>): {age: number} => input.fold(() => ({age: 0}), (input: {age: number}) => input); /* @@ -118,9 +124,13 @@ Let's explore this by converting Optionals to and from Arrays. */ // TODO: Write a function that converts an Optional to an A[] for any type A. - +export const optionalToArray = (input: Optional): A[] => { + return input.fold(() => [], (value) => [value]); +} // TODO: Write a function that converts an A[] to an Optional. If the array has more than one element, only consider the first element. - +export const arrayToOptional = (input: A[]): Optional => { + return Optional.from(input[0]); +} /* One of the most useful functions on Optional is "map". We say this function "maps a function over the Optional". @@ -139,12 +149,14 @@ const x: Optional = Optional.some(3).map((x) => String(x)); // returns O const y: Optional = Optional.none().map((x) => String(x)); // returns Optional.none() // TODO: Write a function that takes an Optional and adds 3 to the number +export const add3 = (number: Optional) => number.map((x) => x + 3); // TODO: Write a function that takes an Optional and prefixes the string with "hello" +export const prefixHello = (input: Optional) => input.map((i) => "hello " + i); /* TODO: If the below function is called, does it return a value or throw an exception? Why should it behave one way or the other? -Answer: ... +Answer: return Optional.none() since there is nothing to runs the function over */ const willItKersplode = (): Optional => { const z = Optional.none(); diff --git a/src/main/ts/Part2Ex4FP.ts b/src/main/ts/Part2Ex4FP.ts index 199bcfe..c121ef8 100644 --- a/src/main/ts/Part2Ex4FP.ts +++ b/src/main/ts/Part2Ex4FP.ts @@ -79,7 +79,15 @@ TODO: Extract a pure function for the logic hiding in this (impure) function type Mode = 'code' | 'design' | 'markdown'; +const hasMode = (m: Mode): boolean => { + return m === 'code' || m === 'design' || m === 'markdown' ? true : false; +} + const switchMode = (m: Mode): void => { + const valid = hasMode(m); + if (valid) { + // do side effect + } // pretend that something useful happens here that causes a side effect }; @@ -120,12 +128,12 @@ const getOrElse1 = (oa: Optional, other: A): A => // Hang on - that looks familiar. The function we pass as the "some" case is the identity function. // TODO: write a version of getOrElse1 using Fun.identity. - +export const getOrElse2 = (oa: Optional, other: A): A => oa.fold(() => other, Fun.identity); // TODO: What happens if you map the identity function over an Optional? -// Answer: ... +// Answer: Get the value of the Optional // TODO: What happens if you map the identity function over an Array? -// Answer: ... +// Answer: Get the value of each element in the array /* In FP, we use a lot of little functions like identity, that seem insignificant on their own, but they come in handy @@ -149,17 +157,19 @@ Again, this looks familiar from our getOrElse1 function above. TODO: rewrite getOrElse1 using both Fun.identity and the "constant" function defined above. */ - +export const getOrElse3 = (oa: Optional, other: A): A => oa.fold(constant(other), Fun.identity) /* TODO: use katamari's Fun.constant in your getOrElse and see if it compiles. */ +export const getOrElse4 = (oa: Optional, other: A): A => oa.fold(Fun.constant(other), Fun.identity) // TODO: Write a function that takes an array of numbers and replaces each value with 9. +export const replaceElementWith9 = (items: number[]) => Arr.map(items, Fun.constant(9)); // TODO: In the previous question, what's the *same* between the input and output values -// Answer: +// Answer: They are the same type (number) and same number of elements?? /* @@ -210,7 +220,7 @@ signature and handling for n-ary functions. Your rule-of-thumb is to use Fun.com */ // TODO: use Fun.compose1 to write a function that doubles a number twice +export const dblX2: (x: number) => number = Fun.compose1(dbl, dbl); // TODO: Rewrite this function to use a single map call and function composition -const dblOs = (oa: Optional): Optional => - oa.map(dbl).map(String); +export const dblOs = (oa: Optional): Optional => oa.map(Fun.compose1(String, dbl)); diff --git a/src/main/ts/Part2Ex5Sugar.ts b/src/main/ts/Part2Ex5Sugar.ts index 27412cc..2338b19 100644 --- a/src/main/ts/Part2Ex5Sugar.ts +++ b/src/main/ts/Part2Ex5Sugar.ts @@ -41,6 +41,16 @@ as the main document. TODO: Use SugarElement's fromHtml and fromText functions to create a few elements. */ +const divWithText = SugarElement.fromHtml("
"); +const divText = SugarElement.fromText("this is a div"); +// maybe not relevant to this question +divWithText.dom.appendChild(divText.dom); + +const paragraph = SugarElement.fromHtml("

", document); +const paragraphText = SugarElement.fromText("this is a paragraph"); +// maybe not relevant to this question +paragraph.dom.appendChild(paragraphText.dom); + /* @@ -52,9 +62,8 @@ We often have to traverse from an element to its relatives. The Traverse module Insert.append(parent, kid); const parent2 = Traverse.parent(kid); - -// TODO: inspect the type of Traverse.parent and explain why that type was used. -// Answer: + // TODO: inspect the type of Traverse.parent and explain why that type was used. + // Answer: It is safer to return an Optional type in case the targeted element doesn't have a parent }; @@ -67,11 +76,15 @@ We often have to traverse from an element to its relatives. The Traverse module Insert.append(parent, kid2); // TODO: starting at kid1, find kid2 + const foundKid2 = Traverse.nextSibling(kid1); // TODO: starting at kid2, find kid1 - + const foundKid1 = Traverse.prevSibling(kid2); // TODO: starting at parent, find both kids + const children = Traverse.children(parent); // TODO: kid2 grew up - give it its own child node + const childOfKid2 = SugarElement.fromTag("div"); + Insert.append(kid2, childOfKid2); }; diff --git a/src/test/ts/part2/Exercise3OptionTest.ts b/src/test/ts/part2/Exercise3OptionTest.ts index ca0c397..1171921 100644 --- a/src/test/ts/part2/Exercise3OptionTest.ts +++ b/src/test/ts/part2/Exercise3OptionTest.ts @@ -1,4 +1,5 @@ import { describe, it } from '@ephox/bedrock-client'; +import { Optional } from '@ephox/katamari'; import { assert } from 'chai'; import * as Ex from '../../../main/ts/Part2Ex3Optional'; @@ -9,12 +10,81 @@ describe('Exercise3OptionTest', () => { assert.isTrue(Ex.getProtocol('frog.com').isNone(), 'no protocol should be found'); assert.isTrue(Ex.getProtocol('://frog.com').isNone(), 'no protocol should be found'); assert.isTrue(Ex.getProtocol('3ttp://frog.com').isNone(), 'malformed protocol should not be registered'); + assert.isTrue(Ex.getProtocol('httpsimple.com').isNone(), 'no protocal should be found'); + assert.isTrue(Ex.getProtocol('http:/simple.com').isNone(), 'no protocal should be found'); }); it('toPositiveInteger', () => { // TODO: write a few test cases + assert.isTrue(Ex.toPositiveInteger(1).equals(Optional.some(1)), 'Positive nunmber'); + assert.isTrue(Ex.toPositiveInteger(0).equals(Optional.none()), 'Zero'); + assert.isTrue(Ex.toPositiveInteger(-4).equals(Optional.none()), 'Negative number'); + }); + + it('isString', () => { + assert.isTrue(Ex.isString('a new string').equals(Optional.some('a new string')), 'Non empty string'); + assert.isTrue(Ex.isString('0').equals(Optional.some('0')), '0 as string'); + assert.isTrue(Ex.isString('').equals(Optional.none()), 'Empty string'); + }); + + it('getNextSibling', () => { + const element = document.createElement('div'); + const child1 = document.createElement('p'); + element.appendChild(child1); + element.appendChild(document.createElement('span')); + assert.isTrue(Ex.getNextSibling(element).equals(Optional.none()), 'element with no sibling'); + assert.isTrue(Ex.getNextSibling(child1).equals(Optional.some(element.childNodes[1])), 'there is a sibling') + }); + + it('getElementAttributes', () => { + const element = document.createElement('div'); + element.setAttribute('id', 'element1'); + element.setAttribute('class', 'css-1'); + assert.isTrue(Ex.getElementAttribute(element, 'id').equals(Optional.some(element.getAttributeNode('id') as Attr)), 'A valid attribute'); + assert.isTrue(Ex.getElementAttribute(element, 'class').equals(Optional.some(element.getAttributeNode('class') as Attr)), 'Another valid attribute'); + assert.isTrue(Ex.getElementAttribute(element, 'invalid').equals(Optional.none()), 'Attribute does not exist'); + }); + + it('doubleNumber', () => { + assert.equal(Ex.doubleNumber(Optional.some(4)), 8, 'a number'); + assert.equal(Ex.doubleNumber(Optional.some(0)), 0, 'some(0)'); + assert.equal(Ex.doubleNumber(Optional.none()), 0, 'none'); + }); + + it('toValueOr', () => { + assert.deepEqual(Ex.toValueOr(Optional.some({age: 9})), {age: 9}, 'A valid value'); + assert.deepEqual(Ex.toValueOr(Optional.none()), {age: 0}, 'Optional.none() as input'); + }); + + it('toValueOrWithFold', () => { + assert.deepEqual(Ex.toValueOrWithFold(Optional.some({age: 9})), {age: 9}, 'A valid value'); + assert.deepEqual(Ex.toValueOrWithFold(Optional.none()), {age: 0}, 'Optional.none() as input'); + }); + + it('optionalToArray', () => { + assert.deepEqual(Ex.optionalToArray(Optional.some(10)), [10], 'A number'); + assert.deepEqual(Ex.optionalToArray(Optional.some({age: 11})), [{age: 11}], 'An object'); + assert.deepEqual(Ex.optionalToArray(Optional.none()), [], 'empty array'); + }); + + it('arrayToOptional', () => { + assert.isTrue(Ex.arrayToOptional([]).equals(Optional.none()), 'should get Optional.none()'); + assert.isTrue(Ex.arrayToOptional([200]).equals(Optional.some(200)), 'should get Optional.some(200)'); + assert.isTrue(Ex.arrayToOptional([300, 200]).equals(Optional.some(300)), 'should only take the first element'); + }); + + it('add3', () => { + assert.isTrue(Ex.add3(Optional.some(10)).equals(Optional.some(13)), 'should be 13'); + assert.isTrue(Ex.add3(Optional.some(7)).equals(Optional.some(10)), 'should be 10'); + assert.isTrue(Ex.add3(Optional.none()).equals(Optional.none()), 'should be none'); + }); + + it('prefixHello', () => { + assert.isTrue(Ex.prefixHello(Optional.some('Mike')).equals(Optional.some('hello Mike')), 'should be hello Mike'); + assert.isTrue(Ex.prefixHello(Optional.some('Tammy')).equals(Optional.some('hello Tammy')), 'should be hello Tammy'); + assert.isTrue(Ex.prefixHello(Optional.none()).equals(Optional.none()), 'should be none'); }); }); // TODO: Now that you have finished all the test files in this directory, -// try running all tests in the "part2" folder all using the `-d` argument in bedrock and specifying the parent directory. +// try running all tests in the 'part2' folder all using the `-d` argument in bedrock and specifying the parent directory. diff --git a/src/test/ts/part2/Exercise4FPTest.ts b/src/test/ts/part2/Exercise4FPTest.ts new file mode 100644 index 0000000..ded98bb --- /dev/null +++ b/src/test/ts/part2/Exercise4FPTest.ts @@ -0,0 +1,42 @@ +import { describe, it } from '@ephox/bedrock-client'; +import {assert} from 'chai'; +import { Fun, Optional } from '@ephox/katamari'; +import * as Ex from '../../../main/ts/Part2Ex4FP'; + +describe('Exercise 4 FP', () => { + it('map identity over array', () => { + assert.deepEqual(['a', 'b', 'c', 'd'].map(Fun.identity), ['a', 'b','c', 'd'], 'wowo'); + }); + + it('getOrElse2', () => { + assert.equal(Ex.getOrElse2(Optional.some(2), 3), 2); + assert.equal(Ex.getOrElse2(Optional.none(), 3), 3); + }); + + it('getOrElse3', () => { + assert.equal(Ex.getOrElse3(Optional.some(2), 3), 2); + assert.equal(Ex.getOrElse3(Optional.none(), 3), 3); + }); + + it('getOrElse4', () => { + assert.equal(Ex.getOrElse4(Optional.some(2), 3), 2); + assert.equal(Ex.getOrElse4(Optional.none(), 3), 3); + }); + + it('replaceElementWith9', () => { + assert.deepEqual(Ex.replaceElementWith9([1, 2, 5, 7]), [9,9,9,9], '4 elements'); + assert.deepEqual(Ex.replaceElementWith9([1]), [9], '1 element'); + assert.deepEqual(Ex.replaceElementWith9([]), [], 'no element'); + }); + + it('dblX2', () => { + assert.equal(Ex.dblX2(2), 8, 'double 2 twice'); + assert.equal(Ex.dblX2(1), 4, 'double 1 twice'); + assert.equal(Ex.dblX2(0), 0, 'double 0 twice'); + }); + + it('dblOs', () => { + assert.isTrue(Ex.dblOs(Optional.some(2)).equals(Optional.some('4')), 'simple number 2'); + assert.isTrue(Ex.dblOs(Optional.some(0)).equals(Optional.some('0')), 'tricky number 0'); + }); +}); \ No newline at end of file diff --git a/src/test/ts/part3/Part3Ex1Test.ts b/src/test/ts/part3/Part3Ex1Test.ts index b4e8bd7..53b3676 100644 --- a/src/test/ts/part3/Part3Ex1Test.ts +++ b/src/test/ts/part3/Part3Ex1Test.ts @@ -56,23 +56,24 @@ describe('Part3Ex1Test', () => { UiFinder.exists(container, 'button[title="Bold"]'); }); - it('has content like an editor', () => { - const editor = hook.editor(); + it('has content like an editor', () => { + const editor = hook.editor(); - editor.setContent(/* TODO */ "

Hello world

"); + editor.setContent("

Hello world

Hello again

Goodbye
"); + - /* - Another useful module from mcagar, TinyAssertions is full of ways to make - sure that the content inside the editor is what you want it to be. Content - presence takes an object where the keys are CSS selectors, and the values - are numbers saying how many elements should match that selector inside the - editor. + /* + Another useful module from mcagar, TinyAssertions is full of ways to make + sure that the content inside the editor is what you want it to be. Content + presence takes an object where the keys are CSS selectors, and the values + are numbers saying how many elements should match that selector inside the + editor. - TODO: Edit the setContent call above to make this test pass. - */ - TinyAssertions.assertContentPresence(editor, { - p: 2, - span: 1 + TODO: Edit the setContent call above to make this test pass. + */ + TinyAssertions.assertContentPresence(editor, { + p: 2, + span: 1, + }); }); - }); }); diff --git a/src/test/ts/part3/Part3Ex2Test.ts b/src/test/ts/part3/Part3Ex2Test.ts index 3ed86e6..ea7456c 100644 --- a/src/test/ts/part3/Part3Ex2Test.ts +++ b/src/test/ts/part3/Part3Ex2Test.ts @@ -40,7 +40,7 @@ describe('Part3Ex3Test', () => { your node is a Text, then the offset is going to tell you how many characters into the text your selection endpoint is. - This concept is summed up really nicely by the builtin "Range" API exported + This concept is summed up really nicely by the builtin 'Range' API exported by the browser. Let's create a Range object, and make sure it works. */ // TinyMCE exposes the built-in JS document.createRange() through the `dom` API @@ -50,9 +50,9 @@ describe('Part3Ex3Test', () => { const contentBody = TinyDom.body(editor); // Let's get the

And some bolded content

const paragraph = Traverse.child(contentBody, 1).getOrDie('Unable to find second child of editor body'); - // And then get the bolded (child 0 would be the text node "And some ") + // And then get the bolded (child 0 would be the text node 'And some ') const strong = Traverse.child(paragraph, 1).getOrDie('Unable to find second child of paragraph'); - // And finally let's get the text node "bolded" + // And finally let's get the text node 'bolded' const text = Traverse.child(strong, 0).getOrDie('Unable to find text inside strong element'); range.setStart(text.dom, 0); @@ -72,9 +72,9 @@ describe('Part3Ex3Test', () => { const contentBody = TinyDom.body(editor); // Let's get the

And some bolded content

const paragraph = Traverse.child(contentBody, 1).getOrDie('Unable to find second child of editor body'); - // And then get the bolded (child 0 would be the text node "And some ") + // And then get the bolded (child 0 would be the text node 'And some ') const strong = Traverse.child(paragraph, 1).getOrDie('Unable to find second child of paragraph'); - // And finally let's get the text node "bolded" + // And finally let's get the text node 'bolded' const text = Traverse.child(strong, 0).getOrDie('Unable to find text inside strong element'); And here's how you'd do all of this with an agar API. @@ -84,7 +84,7 @@ describe('Part3Ex3Test', () => { // Note that Cursors.follow only deals with DOM nodes, not offsets, and it uses the same node // for both the selection start and end node /* - Each number in this array represents a call to "Traverse.child(..., )" + Each number in this array represents a call to 'Traverse.child(..., )' in a loop, walking down step by step from the container to the node you want to end on. Then, to make things even simpler, we can use mcagar to do all of this @@ -100,10 +100,12 @@ describe('Part3Ex3Test', () => { it('operates on the selection', () => { const editor = hook.editor(); - - // TODO: move the selection to around the words "a bit of" + TinyAssertions.assertContentPresence(editor, {span: 0}); + // TODO: move the selection to around the words 'a bit of' + TinySelections.setSelection(editor, [0,0], 'Here is '.length, [0,0], 'Here is a bit of'.length); // TODO: use an editor command to underline that content (https://www.tiny.cloud/docs/advanced/editor-command-identifiers/) + editor.execCommand('underline'); TinyAssertions.assertContent(editor, [ '

Here is a bit of content

', @@ -112,5 +114,9 @@ describe('Part3Ex3Test', () => { ].join('\n')); // TODO: Write an assertion to test your changes (hint: TinyAssertions) + // TinySelections.setSelection(editor, [0, 0], 5, [0], 3); + // selection includes span with text decoration style + TinyAssertions.assertSelection(editor, [0, 1, 0], 0, [0], 2) + assert.equal(editor.selection.getContent(), 'a bit of'); }); });