diff --git a/index.ts b/index.ts
index 4220d3d..95ba616 100644
--- a/index.ts
+++ b/index.ts
@@ -3,6 +3,14 @@ import { IGameFormat as gameFormat } from "./src/state/IGameFormat";
import { IBonus as bonus, IPacket as packet, ITossup as tossup } from "./src/state/IPacket";
import { IPlayer as player } from "./src/state/TeamState";
import { ModaqControl as control, IModaqControlProps as controlProps } from "./src/components/ModaqControl";
+import {
+ IFormattingOptions as iFormattingOptions,
+ parseFormattedText as ftpParseFormattedText,
+ splitFormattedTextIntoWords as ftpSplitFormattedTextIntoWords,
+ defaultPronunciationGuideMarkers as ftpDefaultPronunciationGuideMarkers,
+ defaultReaderDirectives as ftpDefaultReaderDirectives,
+} from "src/parser/FormattedTextParser";
+import { IFormattedText as iFormattedText } from "src/parser/IFormattedText";
export const ModaqControl = control;
@@ -18,4 +26,16 @@ export type IPlayer = player;
export type IGameFormat = gameFormat;
+export type IFormattingOptions = iFormattingOptions;
+
+export type IFormattedText = iFormattedText;
+
export const GameFormats = gameFormats;
+
+export const defaultPronunciationGuideMarkers = ftpDefaultPronunciationGuideMarkers;
+
+export const defaultReaderDirectives = ftpDefaultReaderDirectives;
+
+export const parseFormattedText = ftpParseFormattedText;
+
+export const splitFormattedTextIntoWords = ftpSplitFormattedTextIntoWords;
diff --git a/package.json b/package.json
index bacda4a..9c744f2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "modaq",
- "version": "1.28.0",
+ "version": "1.29.0",
"description": "Quiz Bowl Reader using TypeScript, React, and MobX",
"repository": {
"type": "git",
diff --git a/src/components/Answer.tsx b/src/components/Answer.tsx
index f73c20e..8cd5eae 100644
--- a/src/components/Answer.tsx
+++ b/src/components/Answer.tsx
@@ -9,10 +9,9 @@ import { AppState } from "../state/AppState";
export const Answer = observer(function Answer(props: IAnswerProps): JSX.Element {
const appState: AppState = React.useContext(StateContext);
- const formattedText: IFormattedText[] = FormattedTextParser.parseFormattedText(
- props.text.trimLeft(),
- appState.game.gameFormat.pronunciationGuideMarkers
- );
+ const formattedText: IFormattedText[] = FormattedTextParser.parseFormattedText(props.text.trimLeft(), {
+ pronunciationGuideMarkers: appState.game.gameFormat.pronunciationGuideMarkers,
+ });
return (
diff --git a/src/components/dialogs/ScoresheetDialog.tsx b/src/components/dialogs/ScoresheetDialog.tsx
index 7af2785..b74e775 100644
--- a/src/components/dialogs/ScoresheetDialog.tsx
+++ b/src/components/dialogs/ScoresheetDialog.tsx
@@ -162,7 +162,9 @@ function getUnformattedAnswer(game: GameState, answer: string): string {
answer = answer.substring(0, alternateIndex).trim();
}
- const text = FormattedTextParser.parseFormattedText(answer, game.gameFormat.pronunciationGuideMarkers)
+ const text = FormattedTextParser.parseFormattedText(answer, {
+ pronunciationGuideMarkers: game.gameFormat.pronunciationGuideMarkers,
+ })
.map((line) => line.text)
.join("");
diff --git a/src/parser/FormattedTextParser.ts b/src/parser/FormattedTextParser.ts
index 288a02f..d0cb6af 100644
--- a/src/parser/FormattedTextParser.ts
+++ b/src/parser/FormattedTextParser.ts
@@ -1,12 +1,74 @@
import { IFormattedText } from "./IFormattedText";
-export function parseFormattedText(text: string, pronunciationGuideMarkers?: [string, string]): IFormattedText[] {
+/**
+ * Default pronunciation guide markers used if none are passed into `IFormattingOptions`
+ */
+export const defaultPronunciationGuideMarkers: [string, string] = ["(", ")"];
+
+/**
+ * Default reader directives used if none are passed into `IFormattingOptions`
+ */
+export const defaultReaderDirectives: string[] = ["(emphasize)", "(pause)", "(read slowly)"];
+
+/**
+ * Options for how to parse and format text
+ */
+export interface IFormattingOptions {
+ /**
+ * Two-element array where the first string is the tag for the start of a pronunciation guide and the second string
+ * is the tag for the end. For example, if the pronuncation guide looks like "(guide)", the array would be
+ * [ "(", ")" ]. Pronunciation guides don't count as words and are formatted differently from the rest of the
+ * question text.
+ * If no value is provided, then `defaultPronunciationGuideMarkers` will be used.
+ */
+ pronunciationGuideMarkers?: [string, string];
+
+ /**
+ * Directives for the reader, like "(read slowly)". These don't count as words and are formatted differently from
+ * the rest of the question text.
+ * If no value is provided, then `defaultReaderDirectives` will be used.
+ */
+ readerDirectives?: string[];
+}
+
+/**
+ * Takes text with formatting tags and turns it into an array of texts with formatting information included, such as
+ * which words are bolded.
+ * Note that if the '"' character is used in a pronunciation guide, it will also support '“' and '”', and vice versa.
+ * @param text The text to format, such a question or answerline.
+ * @param options Formtating options, such as what indicates the start of a pronunciation guide.
+ * @returns An array of `IFormattedText` that represents the text with formatting metadata, such as which words are
+ * bolded, underlined, etc.
+ */
+export function parseFormattedText(text: string, options?: IFormattingOptions): IFormattedText[] {
const result: IFormattedText[] = [];
if (text == undefined) {
return result;
}
+ options = options ?? {};
+ const pronunciationGuideMarkers: [[string, string]] = [
+ options.pronunciationGuideMarkers ?? defaultPronunciationGuideMarkers,
+ ];
+
+ // Normalize quotes in pronunciation guides
+ if (pronunciationGuideMarkers[0][0].includes('"') || pronunciationGuideMarkers[0][1].includes('"')) {
+ pronunciationGuideMarkers.push([
+ pronunciationGuideMarkers[0][0].replace(/"/g, "“"),
+ pronunciationGuideMarkers[0][1].replace(/"/g, "”"),
+ ]);
+ }
+
+ if (pronunciationGuideMarkers[0][0].includes("“") || pronunciationGuideMarkers[0][1].includes("”")) {
+ pronunciationGuideMarkers.push([
+ pronunciationGuideMarkers[0][0].replace(/“/g, '"'),
+ pronunciationGuideMarkers[0][1].replace(/”/g, '"'),
+ ]);
+ }
+
+ const readerDirectives: string[] | undefined = options.readerDirectives ?? defaultReaderDirectives;
+
let bolded = false;
let emphasized = false;
let underlined = false;
@@ -15,26 +77,34 @@ export function parseFormattedText(text: string, pronunciationGuideMarkers?: [st
let pronunciation = false;
let startIndex = 0;
+ let extraTags = "";
+ for (const pronunciationGuideMarker of pronunciationGuideMarkers) {
+ extraTags += `|${escapeRegExp(pronunciationGuideMarker[0])}|${escapeRegExp(pronunciationGuideMarker[1])}`;
+ }
+
+ if (readerDirectives) {
+ extraTags += `|${readerDirectives.map((directive) => escapeRegExp(directive)).join("|")}`;
+ }
+
// If we need to support older browswers, use RegExp, exec, and a while loop. See
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll
- const matchIterator: IterableIterator =
- pronunciationGuideMarkers == undefined
- ? text.matchAll(/<\/?em>|<\/?req>|<\/?b>|<\/?u>|<\/?sub>|<\/?sup>/gi)
- : text.matchAll(
- new RegExp(
- `<\\/?em>|<\\/?req>|<\\/?b>|<\\/?u>|<\\/?sub>|<\\/?sup>|${escapeRegExp(
- pronunciationGuideMarkers[0]
- )}|${escapeRegExp(pronunciationGuideMarkers[1])}`,
- "gi"
- )
- );
+ const matchIterator: IterableIterator = text.matchAll(
+ new RegExp(`<\\/?em>|<\\/?req>|<\\/?b>|<\\/?u>|<\\/?sub>|<\\/?sup>${extraTags}`, "gi")
+ );
for (const match of matchIterator) {
// For the end of the pronunciation guide, we want to include it in the string, so add it to the current slice
- const tagInTextLength: number =
- pronunciationGuideMarkers != undefined && match[0].toLowerCase() === pronunciationGuideMarkers[1]
- ? pronunciationGuideMarkers[1].length
- : 0;
+ // TODO: Do we need to do this with reader directives?
+ const tag: string = match[0];
+ const normalizedTag: string = tag.toLowerCase();
+ let tagInTextLength = 0;
+ for (const pronunciationGuideMarker of pronunciationGuideMarkers) {
+ if (normalizedTag === pronunciationGuideMarker[1].toLowerCase()) {
+ tagInTextLength = pronunciationGuideMarker[1].length;
+ break;
+ }
+ }
+
const matchIndex: number = match.index ?? 0;
const slice: string = text.substring(startIndex, matchIndex + tagInTextLength);
@@ -52,9 +122,8 @@ export function parseFormattedText(text: string, pronunciationGuideMarkers?: [st
}
// Once we got the slice of text, toggle the attribute for the next slice
- const tag: string = match[0];
let skipTag = true;
- switch (tag.toLowerCase()) {
+ switch (normalizedTag) {
case "":
emphasized = true;
break;
@@ -94,16 +163,37 @@ export function parseFormattedText(text: string, pronunciationGuideMarkers?: [st
superscripted = false;
break;
default:
- if (pronunciationGuideMarkers) {
- if (tag === pronunciationGuideMarkers[0].toLowerCase()) {
+ let pronunciationGuideMatched = false;
+ for (const pronunciationGuideMarker of pronunciationGuideMarkers) {
+ if (normalizedTag === pronunciationGuideMarker[0].toLowerCase()) {
skipTag = false;
pronunciation = true;
- break;
- } else if (tag === pronunciationGuideMarkers[1].toLowerCase()) {
+ pronunciationGuideMatched = true;
+ } else if (normalizedTag === pronunciationGuideMarker[1].toLowerCase()) {
pronunciation = false;
- break;
+ pronunciationGuideMatched = true;
}
}
+
+ if (pronunciationGuideMatched) {
+ break;
+ }
+
+ if (readerDirectives.some((directive) => directive.trim().toLowerCase() === normalizedTag)) {
+ // Treat it like a pronunciation guide for this one specific word
+ const readerDirectiveText: IFormattedText = {
+ text: tag,
+ bolded,
+ emphasized,
+ underlined,
+ subscripted,
+ superscripted,
+ pronunciation: true,
+ };
+ result.push(readerDirectiveText);
+ break;
+ }
+
throw `Unknown match: ${tag}`;
}
@@ -133,17 +223,20 @@ export function parseFormattedText(text: string, pronunciationGuideMarkers?: [st
// TODO: Look into removing the dependency with parseFormattedText, so that we only do one pass over the string instead
// of two passes.
-export function splitFormattedTextIntoWords(
- text: string,
- pronunciationGuideMarkers?: [string, string]
-): IFormattedText[][] {
+/**
+ * Takes text with formatting tags and splits it into an array of words with formatting information for each word.
+ * @param text The text to format, such a question or answerline.
+ * @param options Formtating options, such as what indicates the start of a pronunciation guide.
+ * @returns An array of words represented as an `IFormattedText[]` representing all the formatting in that word.
+ */
+export function splitFormattedTextIntoWords(text: string, options?: IFormattingOptions): IFormattedText[][] {
// We need to take the list of formatted text and split them up into individual words.
// Algorithm: For each piece of formatted text, go through and split the text by the spaces in it.
// If there are no spaces, then add it to a variable tracking the last word.
// If there are spaces, add the last word to the list, and then add each non-empty segment (i.e. non-space) to the
// list, except for the last one. If the last segment isn't empty, set that as the "last word", and continue going
// through the list of formatted texts.
- const formattedText: IFormattedText[] = parseFormattedText(text, pronunciationGuideMarkers);
+ const formattedText: IFormattedText[] = parseFormattedText(text, options);
const splitFormattedText: IFormattedText[][] = [];
diff --git a/src/parser/IFormattedText.ts b/src/parser/IFormattedText.ts
index c398e2f..decbdfb 100644
--- a/src/parser/IFormattedText.ts
+++ b/src/parser/IFormattedText.ts
@@ -1,8 +1,23 @@
export interface IFormattedText {
+ /**
+ * The text of this fragment
+ */
text: string;
bolded: boolean;
+
+ /**
+ * If text is emphasized, which is italicized.
+ */
emphasized: boolean;
+
+ /**
+ * `true` if this text should be formatted like a pronunciation guide or reader directive.
+ */
pronunciation?: boolean;
+
+ /**
+ * Obsolete. Use bolded and underlined instead.
+ */
required?: boolean;
underlined?: boolean;
subscripted?: boolean;
diff --git a/src/state/PacketState.ts b/src/state/PacketState.ts
index 3b3f0b5..3f4c233 100644
--- a/src/state/PacketState.ts
+++ b/src/state/PacketState.ts
@@ -76,7 +76,9 @@ export class Tossup implements IQuestion {
let powerMarkerIndex = 0;
for (let i = 0; i < format.powers.length; i++) {
const powerMarker: string = format.powers[i].marker.trim();
- const currentPowerMarkerIndex = words.indexOf(powerMarker, powerMarkerIndex);
+ const currentPowerMarkerIndex = words.findIndex(
+ (value, index) => index >= powerMarkerIndex && value.startsWith(powerMarker)
+ );
if (currentPowerMarkerIndex === -1) {
continue;
}
@@ -129,7 +131,7 @@ export class Tossup implements IQuestion {
let canBuzzOn = true;
let index: number = wordIndex;
const trimmedText: string = fullText.trim();
- const powerMarkerIndex: number = format.powers.findIndex((power) => power.marker === trimmedText);
+ const powerMarkerIndex: number = format.powers.findIndex((power) => trimmedText.startsWith(power.marker));
if (isLastWord) {
// Last word should always be the terminal character, which can't be a power or in a pronunciation guide
wordIndex++;
@@ -173,9 +175,9 @@ export class Tossup implements IQuestion {
private formattedQuestionText(format: IGameFormat): IFormattedText[][] {
// Include the ■ to give an end of question marker
- return FormattedTextParser.splitFormattedTextIntoWords(this.question, format.pronunciationGuideMarkers).concat([
- [{ text: "■END■", bolded: true, emphasized: false, required: false, pronunciation: false }],
- ]);
+ return FormattedTextParser.splitFormattedTextIntoWords(this.question, {
+ pronunciationGuideMarkers: format.pronunciationGuideMarkers,
+ }).concat([[{ text: "■END■", bolded: true, emphasized: false, required: false, pronunciation: false }]]);
}
}
@@ -197,7 +199,9 @@ export class Bonus {
}
export function getBonusWords(text: string, format: IGameFormat): IFormattedText[] {
- return FormattedTextParser.parseFormattedText(text, format.pronunciationGuideMarkers);
+ return FormattedTextParser.parseFormattedText(text, {
+ pronunciationGuideMarkers: format.pronunciationGuideMarkers,
+ });
}
export type ITossupWord = IBuzzableTossupWord | INonbuzzableTossupWord;
diff --git a/tests/FormattedTextParserTests.ts b/tests/FormattedTextParserTests.ts
index 24ebeb1..41d2cac 100644
--- a/tests/FormattedTextParserTests.ts
+++ b/tests/FormattedTextParserTests.ts
@@ -119,10 +119,9 @@ describe("FormattedTextParserTests", () => {
});
it("Pronunciation guide", () => {
const textToFormat = "This text is mine (mein).";
- const result: IFormattedText[] = FormattedTextParser.parseFormattedText(
- textToFormat,
- GameFormats.ACFGameFormat.pronunciationGuideMarkers
- );
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: GameFormats.ACFGameFormat.pronunciationGuideMarkers,
+ });
expect(result).to.deep.equal([
{
text: "This text is mine ",
@@ -155,10 +154,9 @@ describe("FormattedTextParserTests", () => {
});
it("Bolded pronunciation guide", () => {
const textToFormat = "Solano Lopez (LOW-pez) was in this war.";
- const result: IFormattedText[] = FormattedTextParser.parseFormattedText(
- textToFormat,
- GameFormats.ACFGameFormat.pronunciationGuideMarkers
- );
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: GameFormats.ACFGameFormat.pronunciationGuideMarkers,
+ });
expect(result).to.deep.equal([
{
text: "Solano Lopez ",
@@ -191,7 +189,9 @@ describe("FormattedTextParserTests", () => {
});
it("Non-parentheses pronunciation guide", () => {
const textToFormat = "This text is mine [mein].";
- const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, ["[", "]"]);
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["[", "]"],
+ });
expect(result).to.deep.equal([
{
text: "This text is mine ",
@@ -224,7 +224,9 @@ describe("FormattedTextParserTests", () => {
});
it("Different pronunciation guide", () => {
const textToFormat = "This text is mine (mein).";
- const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, ["[", "]"]);
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["[", "]"],
+ });
expect(result).to.deep.equal([
{
text: "This text is mine (mein).",
@@ -237,6 +239,307 @@ describe("FormattedTextParserTests", () => {
},
]);
});
+ it("Special quotes in text with normal quotes in pronunciation guide", () => {
+ const textToFormat = "This text is mine (“mein”).";
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ['("', '")'],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This text is mine ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: "(“mein”)",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: ".",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Normal quotes in text with special quotes in pronunciation guide", () => {
+ const textToFormat = 'This text is mine ("mein").';
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["(“", "”)"],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This text is mine ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: '("mein")',
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: ".",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Mixed quotes in text (special, normal) in pronunciation guide", () => {
+ const textToFormat = 'This text is mine (“mein").';
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["(“", "”)"],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This text is mine ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: '(“mein")',
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: ".",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Mixed quotes in text (normal, special) in pronunciation guide", () => {
+ const textToFormat = 'This text is mine ("mein”).';
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["(“", "”)"],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This text is mine ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: '("mein”)',
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: ".",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Special quotes in wrong order in pronunciation guide", () => {
+ const textToFormat = "This text is mine (”mein“).";
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ['("', '")'],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This text is mine (”mein“).",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Case insensitive normal quotes in text with special quotes in pronunciation guide", () => {
+ const textToFormat = 'This text is mine (a"mein"a).';
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["(A“", "”A)"],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This text is mine ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: '(a"mein"a)',
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: ".",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Default reader directives", () => {
+ const textToFormat =
+ "This (Emphasize) equation is proportional to (read slowly) a minus x, plus (pause) 1.";
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["[", "]"],
+ // readerDirectives: ["(emphasize)", "(read slowly)", "(pause)"],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: "(Emphasize)",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: " equation is proportional to ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: "(read slowly)",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: " a minus x, plus ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: "(pause)",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: " 1.",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
+ it("Explicit reader directives", () => {
+ const textToFormat = "This (Emphasize) equation is proportional to (slowly) a minus x, plus (pause) 1.";
+ const result: IFormattedText[] = FormattedTextParser.parseFormattedText(textToFormat, {
+ pronunciationGuideMarkers: ["[", "]"],
+ readerDirectives: ["(slowly)"],
+ });
+ expect(result).to.deep.equal([
+ {
+ text: "This (Emphasize) equation is proportional to ",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ {
+ text: "(slowly)",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: true,
+ },
+ {
+ text: " a minus x, plus (pause) 1.",
+ bolded: false,
+ emphasized: false,
+ underlined: false,
+ subscripted: false,
+ superscripted: false,
+ pronunciation: false,
+ },
+ ]);
+ });
it("Emphasized then required", () => {
const text = "Before emphasized then in between required then done.";
const result: IFormattedText[] = FormattedTextParser.parseFormattedText(text);
@@ -473,10 +776,9 @@ describe("FormattedTextParserTests", () => {
});
it("Pronunciation", () => {
const textToFormat = "There is a pronunciation guide (GUY-de) in this question.";
- const result: IFormattedText[][] = FormattedTextParser.splitFormattedTextIntoWords(
- textToFormat,
- GameFormats.ACFGameFormat.pronunciationGuideMarkers
- );
+ const result: IFormattedText[][] = FormattedTextParser.splitFormattedTextIntoWords(textToFormat, {
+ pronunciationGuideMarkers: GameFormats.ACFGameFormat.pronunciationGuideMarkers,
+ });
const expected: IFormattedText[][] = textToFormat.split(/\s+/g).map((word, index) => {
return [
{
diff --git a/tests/PacketStateTests.ts b/tests/PacketStateTests.ts
index d855388..62957c7 100644
--- a/tests/PacketStateTests.ts
+++ b/tests/PacketStateTests.ts
@@ -58,6 +58,37 @@ describe("PacketStateTests", () => {
expect(formattedWord.underlined).to.be.false;
expect(formattedWord.pronunciation).to.be.true;
});
+ it("formattedQuestionText has power marker", () => {
+ const tossup: Tossup = new Tossup("The power marker (*) is here.", "Answer");
+ const formattedText: IFormattedText[][] = tossup.getWords(powersGameFormat).map((word) => word.word);
+ expect(formattedText.length).to.be.greaterThan(1);
+ expect(formattedText[3].length).to.equal(1);
+ const formattedWord: IFormattedText = formattedText[3][0];
+ expect(formattedWord.text).to.equal("(*)");
+ expect(formattedWord.bolded).to.be.false;
+ expect(formattedWord.emphasized).to.be.false;
+ expect(formattedWord.underlined).to.be.false;
+ expect(formattedWord.pronunciation).to.be.false;
+ });
+ it("formattedQuestionText has power marker with punctuation after it", () => {
+ const tossup: Tossup = new Tossup("The power marker (*), I think.", "Answer");
+ const formattedText: IFormattedText[][] = tossup.getWords(powersGameFormat).map((word) => word.word);
+ expect(formattedText.length).to.be.greaterThan(1);
+ expect(formattedText[3].length).to.equal(2);
+ const formattedFirstPart: IFormattedText = formattedText[3][0];
+ expect(formattedFirstPart.text).to.equal("(*)");
+ expect(formattedFirstPart.bolded).to.be.false;
+ expect(formattedFirstPart.emphasized).to.be.false;
+ expect(formattedFirstPart.underlined).to.be.false;
+ expect(formattedFirstPart.pronunciation).to.be.false;
+
+ const formattedSecondWord: IFormattedText = formattedText[3][1];
+ expect(formattedSecondWord.text).to.equal(",");
+ expect(formattedSecondWord.bolded).to.be.false;
+ expect(formattedSecondWord.emphasized).to.be.false;
+ expect(formattedSecondWord.underlined).to.be.false;
+ expect(formattedSecondWord.pronunciation).to.be.false;
+ });
});
// Need tests for getBonusWords?
@@ -246,6 +277,14 @@ describe("PacketStateTests", () => {
const points: number = tossup.getPointsAtPosition(superpowersGameFormat, 2);
expect(points).to.equal(15);
});
+ it("In power with punctuation after power marker", () => {
+ const tossup: Tossup = new Tossup("This is my (*), question", "Answer");
+ const points: number = tossup.getPointsAtPosition(powersGameFormat, 2);
+ expect(points).to.equal(15);
+
+ const pointsAfter: number = tossup.getPointsAtPosition(powersGameFormat, 3);
+ expect(pointsAfter).to.equal(10);
+ });
// Tossups include a special character to mark the end of the question, which is after the last word in the
// question, so the last index will be one greater than the number of words in the question.