• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / BotFramework-Composer / 9116142622

16 May 2024 04:49PM UTC coverage: 54.421% (-0.003%) from 54.424%
9116142622

push

github

web-flow
fix: [#9441] Multi-line SSML causes escaping issues (#9444)

* Remove line breaks from SSML tags

* Update yarn-berry.lock

* Update yarn-berry.lock files

* Update yarn-berry.lock

* Update yarn-berry file

* Fix hash

---------

Co-authored-by: tracyboehrer <tracyboehrer@users.noreply.github.com>
Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
Co-authored-by: Joel Mut <62260472+sw-joelmut@users.noreply.github.com>
Co-authored-by: Joel Mut <joel.mut@southworks.com>

7577 of 18339 branches covered (41.32%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

19745 of 31866 relevant lines covered (61.96%)

26.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.25
/Composer/packages/lib/code-editor/src/lg/hooks/useStringArray.ts
1
/* eslint-disable security/detect-unsafe-regex */
2
// Copyright (c) Microsoft Corporation.
3
// Licensed under the MIT License.
4

5
import { LgTemplate } from '@bfc/shared';
6
import React from 'react';
2✔
7
import { TemplateBodyItem, parseTemplateBody, templateBodyItemsToString } from '@bfc/shared';
1✔
8

9
import { ArrayBasedStructuredResponseItem, PartialStructuredResponse } from '../types';
10
import { getTemplateId } from '../../utils/structuredResponse';
1✔
11
import { LGOption } from '../../utils/types';
12

13
const multiLineBlockSymbol = '```';
1✔
14

15
const getInitialItems = <T extends ArrayBasedStructuredResponseItem>(
1✔
16
  response: T,
17
  lgTemplates?: readonly LgTemplate[],
18
  focusOnMount?: boolean,
19
): TemplateBodyItem[] => {
20
  const templateId = getTemplateId(response);
2✔
21
  const template = lgTemplates?.find(({ name }) => name === templateId);
1✔
22
  return response?.value && template?.body
2!
23
    ? parseTemplateBody(template?.body)
3!
24
        // Remove LG template multiline block symbol
25
        .map((s) => ({ ...s, value: s.value.replace(/```/g, '') })) ?? []
3!
26
    : response?.value.map((v) => ({ kind: 'variation', value: v })) ||
1!
27
        (focusOnMount ? [{ kind: 'variation', value: '' }] : []);
×
28
};
29

30
const fixMultilineItems = (items: TemplateBodyItem[]) => {
1✔
31
  return items.map((item) => {
×
32
    if (item.kind === 'variation' && /\r?\n/g.test(item.value)) {
×
33
      // if it's an SSML tag, remove the line breaks.
NEW
34
      if (/^<speak/g.test(item.value.trim())) {
×
NEW
35
        return {
×
36
          ...item,
37
          value: item.value.replace(/[\r\n]+/g, ''),
38
        };
39
      }
UNCOV
40
      return {
×
41
        ...item,
42
        // Escape all un-escaped -
43
        value: `${multiLineBlockSymbol}${item.value.replace(/(?<!\\)-/g, '\\-')}${multiLineBlockSymbol}`,
44
      };
45
    }
46

47
    return item;
×
48
  });
49
};
50

51
export const useStringArray = <T extends ArrayBasedStructuredResponseItem>(
1✔
52
  kind: 'Text' | 'Speak',
53
  structuredResponse: T,
54
  callbacks: {
55
    onRemoveTemplate: (templateId: string) => void;
56
    onTemplateChange: (templateId: string, body?: string) => void;
57
    onUpdateResponseTemplate: (response: PartialStructuredResponse) => void;
58
  },
59
  options?: {
60
    focusOnMount?: boolean;
61
    lgOption?: LGOption;
62
    lgTemplates?: readonly LgTemplate[];
63
  },
64
) => {
65
  const newTemplateNameSuffix = React.useMemo(() => kind.toLowerCase(), [kind]);
2✔
66

67
  const { onRemoveTemplate, onTemplateChange, onUpdateResponseTemplate } = callbacks;
2✔
68
  const { lgOption, lgTemplates, focusOnMount } = options || {};
2!
69

70
  const [templateId, setTemplateId] = React.useState(getTemplateId(structuredResponse));
71
  const [items, setItems] = React.useState<TemplateBodyItem[]>(
72
    getInitialItems(structuredResponse, lgTemplates, focusOnMount),
73
  );
74

75
  const onChange = React.useCallback(
76
    (newItems: TemplateBodyItem[]) => {
77
      setItems(newItems);
×
78
      // Fix variations that are multiline
79
      // If only one item but it's multiline, still use helper LG template
80
      const fixedNewItems = fixMultilineItems(newItems);
×
81
      const id = templateId || `${lgOption?.templateId}_${newTemplateNameSuffix}`;
×
82
      if (!fixedNewItems.length) {
×
83
        setTemplateId(id);
×
84
        onUpdateResponseTemplate({ [kind]: { kind, value: [], valueType: 'direct' } });
×
85
        onRemoveTemplate(id);
×
86
      } else {
87
        setTemplateId(id);
×
88
        onUpdateResponseTemplate({ [kind]: { kind, value: [`\${${id}()}`], valueType: 'template' } });
×
89
        onTemplateChange(id, templateBodyItemsToString(fixedNewItems));
×
90
      }
91
    },
92
    [kind, newTemplateNameSuffix, lgOption, templateId, onRemoveTemplate, onTemplateChange, onUpdateResponseTemplate],
93
  );
94

95
  return { items, onChange };
2✔
96
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc