diff --git a/docs/customization/plug-points.md b/docs/customization/plug-points.md
index 9d7303bb1e7..e7ab11abb5c 100644
--- a/docs/customization/plug-points.md
+++ b/docs/customization/plug-points.md
@@ -30,7 +30,7 @@ For example, you can implement a multiple-phrase filter:
const MultiplePhraseFilterPlugin = function() {
return {
fn: {
- opsFilter: (taggedOps, phrase) => {
+ opsFilter: (taggedOps, phrase, filterConfig) => {
const phrases = phrase.split(", ")
return taggedOps.filter((val, key) => {
diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md
index 77740567fd6..ad3f2b63ab7 100644
--- a/docs/usage/configuration.md
+++ b/docs/usage/configuration.md
@@ -53,7 +53,11 @@ Parameter name | Docker variable | Description
`defaultModelRendering` | `DEFAULT_MODEL_RENDERING` | `String=["example"*, "model"]`. Controls how the model is shown when the API is first rendered. (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links.)
`displayRequestDuration` | `DISPLAY_REQUEST_DURATION` | `Boolean=false`. Controls the display of the request duration (in milliseconds) for "Try it out" requests.
`docExpansion` | `DOC_EXPANSION` | `String=["list"*, "full", "none"]`. Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing).
-`filter` | `FILTER` | `Boolean=false OR String`. If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag.
+`filter` | `FILTER` | `Boolean=false OR String`. If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the filter expression.
+`filterConfig` | _Unavailable_ | `Object={}`. A JavaScript object describing how to filter.
+`filterConfig.isRegexFilter` | _Unavailable_ | `Boolean=false`. Controls the filter mode. By default, the filter is matched anywhere inside the tag. If set to true it will treat the provided filter value as regular expression.
+`filterConfig.matchCase` | _Unavailable_ | `Boolean=true`. Controls the case matching mode. By default, filtering is case sensitive. It can be set to false to match case insensitive.
+`filterConfig.matchWords` | _Unavailable_ | `Boolean=false`. Controls the full words matching mode. By default, it is disabled. If set to true it will match only full words.
`maxDisplayedTags` | `MAX_DISPLAYED_TAGS` | `Number`. If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations.
`operationsSorter` | _Unavailable_ | `Function=(a => a)`. Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged.
`showExtensions` | `SHOW_EXTENSIONS` | `Boolean=false`. Controls the display of vendor extension (`x-`) fields and values for Operations, Parameters, Responses, and Schema.
@@ -167,4 +171,4 @@ SPEC="{ \"openapi\": \"3.0.0\" }"
```sh
SUPPORTED_SUBMIT_METHODS=['get', 'post']
URLS=[ { url: 'http://petstore.swagger.io/v2/swagger.json', name: 'Petstore' } ]
-```
\ No newline at end of file
+```
diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx
index b50e2afb4cf..d377a3bdbc8 100644
--- a/src/core/components/operations.jsx
+++ b/src/core/components/operations.jsx
@@ -46,10 +46,11 @@ export default class Operations extends React.Component {
} = getConfigs()
let filter = layoutSelectors.currentFilter()
+ let filterConfig = layoutSelectors.currentFilterConfig()
if (filter) {
if (filter !== true && filter !== "true" && filter !== "false") {
- taggedOps = fn.opsFilter(taggedOps, filter)
+ taggedOps = fn.opsFilter(taggedOps, filter, filterConfig)
}
}
diff --git a/src/core/containers/filter.jsx b/src/core/containers/filter.jsx
index 872297f055e..10bd8a64eb3 100644
--- a/src/core/containers/filter.jsx
+++ b/src/core/containers/filter.jsx
@@ -1,7 +1,14 @@
import React from "react"
import PropTypes from "prop-types"
+import { isFunc } from "../utils"
export default class FilterContainer extends React.Component {
+ constructor() {
+ super()
+ this.state = {
+ savedCursorOffset: 0,
+ }
+ }
static propTypes = {
specSelectors: PropTypes.object.isRequired,
@@ -10,35 +17,235 @@ export default class FilterContainer extends React.Component {
getComponent: PropTypes.func.isRequired,
}
+ componentDidMount() {
+ if (this.inputRef) {
+ const filter = this.props.layoutSelectors.currentFilter()
+ this.inputRef.innerText = filter === true || filter === "true" ? "" : filter
+ this.inputRef.addEventListener("keypress", e => {
+ if (e.key === "Enter") {
+ e.preventDefault()
+ }
+ })
+ this.inputRef.addEventListener("paste", (e) => {
+ const content = e.clipboardData.getData("text/plain")
+ document.execCommand("insertText", false, content
+ .replace(/\r?\n|\r/g, "")
+ .replace(new RegExp(String.fromCharCode(160), "g"), " "))
+ e.preventDefault()
+ return false
+ })
+ }
+ }
+
+ getFilterConfig = () => {
+ const configMap = this.props.layoutSelectors.currentFilterConfig()
+ if (isFunc(configMap.toJS)) {
+ return configMap.toJS()
+ }
+ return configMap
+ }
+
onFilterChange = (e) => {
- const {target: {value}} = e
- this.props.layoutActions.updateFilter(value)
+ if (this.inputRef.innerHTML.trim() === "
") {
+ this.inputRef.innerHTML = ""
+ }
+ this.setState({ savedCursorOffset: document.getSelection().focusOffset })
+ const { target: { innerText } } = e
+ this.props.layoutActions.updateFilter(innerText)
+ }
+ onRegexToggle = () => {
+ const currentFilterConfig = this.getFilterConfig()
+ this.props.layoutActions.updateFilterConfig({
+ ...currentFilterConfig,
+ matchWords: false,
+ isRegexFilter: !currentFilterConfig.isRegexFilter,
+ })
+ this.restoreInputFocus()
+ }
+
+ restoreInputFocus() {
+ this.inputRef.focus()
+ if (this.state.savedCursorOffset !== undefined) {
+ document.getSelection().collapse(this.inputRef.firstChild, this.state.savedCursorOffset)
+ }
+ }
+
+ onMatchCaseToggle = () => {
+ const currentFilterConfig = this.getFilterConfig()
+ this.props.layoutActions.updateFilterConfig({
+ ...currentFilterConfig,
+ matchCase: !currentFilterConfig.matchCase,
+ })
+ this.restoreInputFocus()
+ }
+
+ onMatchWordsToggle = () => {
+ const currentFilterConfig = this.getFilterConfig()
+ this.props.layoutActions.updateFilterConfig({
+ ...currentFilterConfig,
+ matchWords: !currentFilterConfig.matchWords,
+ })
+ this.restoreInputFocus()
+ }
+
+ onSearchLocationChange = (locationOption) => {
+ const currentFilterConfig = this.getFilterConfig()
+ this.props.layoutActions.updateFilterConfig({
+ ...currentFilterConfig,
+ searchLocation: locationOption.target.value,
+ })
+ this.restoreInputFocus()
}
- render () {
- const {specSelectors, layoutSelectors, getComponent} = this.props
+ moveCaret(win, charCount) {
+ let sel, range
+ if (win.getSelection) {
+ // IE9+ and other browsers
+ sel = win.getSelection()
+ if (sel.rangeCount > 0) {
+ const textNode = this.inputRef.firstChild
+ const newOffset = sel.focusOffset + charCount
+ sel.collapse(textNode, Math.min(textNode.length, newOffset))
+ }
+ } else if ((sel = win.document.selection)) {
+ // IE <= 8
+ if (sel.type !== "Control") {
+ range = sel.createRange()
+ range.move("character", charCount)
+ range.select()
+ }
+ }
+ this.setState({ savedCursorOffset: document.getSelection().focusOffset })
+ }
+
+ render() {
+ const { specSelectors, layoutSelectors, getComponent } = this.props
const Col = getComponent("Col")
const isLoading = specSelectors.loadingStatus() === "loading"
const isFailed = specSelectors.loadingStatus() === "failed"
const filter = layoutSelectors.currentFilter()
+ const filterConfig = this.getFilterConfig()
+ const isRegexFilter = filterConfig.isRegexFilter
+
+ const formStyle = {
+ display: "flex",
+ flexDirection: "row",
+ border: "1px solid grey",
+ margin: "20px 0",
+ background: "white",
+ padding: "5px",
+ }
+
+ const inputStyle = {
+ flexShrink: 1,
+ padding: "0px !important",
+ border: "none",
+ margin: 0,
+ overflow: "overlay",
+ outline: "none",
+ }
+
+ const btnStyle = {
+ marginRight: "5px",
+ padding: "5px 10px",
+ fontSize: "larger",
+ }
+
+ const separatorStyle = {
+ flexGrow: 1,
+ margin: "0 5px",
+ borderRight: "1px solid grey",
+ }
+
+ const regexPreAndSuffixStyle = {
+ height: "100%",
+ fontSize: "larger",
+ color: "grey",
+ alignSelf: "center",
+ }
+
+ const btnClassNames = ["btn"]
const classNames = ["operation-filter-input"]
if (isFailed) classNames.push("failed")
if (isLoading) classNames.push("loading")
+ const makeActiveBtn = style => ({ ...style, color: "#49cc90", borderColor: "#49cc90" })
+
+ let regexBtnStyle
+ if (isRegexFilter) {
+ regexBtnStyle = makeActiveBtn(btnStyle)
+ } else {
+ regexBtnStyle = { ...btnStyle }
+ regexBtnStyle.border = "none"
+ }
+
+ let matchCaseBtnStyle
+ if (filterConfig.matchCase) {
+ matchCaseBtnStyle = makeActiveBtn(btnStyle)
+ } else {
+ matchCaseBtnStyle = { ...btnStyle }
+ matchCaseBtnStyle.border = "none"
+ }
+
+ let matchWordsBtnStyle
+ if (filterConfig.matchWords) {
+ matchWordsBtnStyle = makeActiveBtn(btnStyle)
+ } else {
+ matchWordsBtnStyle = { ...btnStyle }
+ matchWordsBtnStyle.border = "none"
+ }
+
return (
{filter === null || filter === false || filter === "false" ? null :
)
}
+
+ getRef(r) {
+ this.inputRef = r
+ }
}
diff --git a/src/core/index.js b/src/core/index.js
index 1007d87c740..2ccb3b8c4d2 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -35,6 +35,12 @@ export default function SwaggerUI(opts) {
docExpansion: "list",
maxDisplayedTags: null,
filter: null,
+ filterConfig: {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ searchLocation: "tag",
+ },
validatorUrl: "https://validator.swagger.io/validator",
oauth2RedirectUrl: `${window.location.protocol}//${window.location.host}/oauth2-redirect.html`,
persistAuthorization: false,
@@ -101,7 +107,8 @@ export default function SwaggerUI(opts) {
state: deepExtend({
layout: {
layout: constructorConfig.layout,
- filter: constructorConfig.filter
+ filter: constructorConfig.filter,
+ filterConfig: constructorConfig.filterConfig,
},
spec: {
spec: "",
diff --git a/src/core/plugins/filter/opsFilter.js b/src/core/plugins/filter/opsFilter.js
index fecf262f40d..6e0e01c2f3a 100644
--- a/src/core/plugins/filter/opsFilter.js
+++ b/src/core/plugins/filter/opsFilter.js
@@ -1,3 +1,76 @@
-export default function(taggedOps, phrase) {
- return taggedOps.filter((tagObj, tag) => tag.indexOf(phrase) !== -1)
+import { isFunc } from "../../utils"
+
+export default function(taggedOps, phrase, filterConfig = {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ searchLocation: "tag"
+}) {
+ if(isFunc(filterConfig.toJS)) {
+ filterConfig = filterConfig.toJS()
+ }
+ filterConfig.searchLocation ??= "tag"
+ if(phrase === "") {
+ return taggedOps
+ }
+ if (filterConfig.isRegexFilter) {
+ let expr
+ try {
+ expr = new RegExp(
+ filterConfig.matchWords
+ ? `\\b${phrase}\\b`
+ : phrase,
+ !filterConfig.matchCase ? "i" : "",
+ )
+ } catch {
+ // noop
+ }
+ if (expr) {
+ switch (filterConfig.searchLocation) {
+ case "tag":
+ return taggedOps.filter((tagObj, tag) => expr.test(tag))
+ case "route":
+ return taggedOps.mapEntries(([k,v]) => {
+ const newValue = v.set(
+ "operations",
+ v.get("operations")
+ .filter(op => expr.test(op.get("path")))
+ )
+ return [k, newValue]
+ }).filter((tagObj) => tagObj.get("operations").size !== 0)
+ }
+ }
+ }
+ let isMatch = (tag) => tag.indexOf(phrase) !== -1
+ if (filterConfig.matchWords) {
+ isMatch = (tag) => {
+ const index = tag.indexOf(phrase)
+ if (index !== -1) {
+ return (index === 0 || tag[index - 1] === " ") &&
+ (index + phrase.length === tag.length || tag[index + phrase.length] === " ")
+ }
+ return false
+ }
+ }
+ if (!filterConfig.matchCase) {
+ phrase = phrase.toLowerCase()
+ }
+ switch (filterConfig.searchLocation) {
+ case "tag":
+ return taggedOps
+ .filter((tagObj, tag) => isMatch(!filterConfig.matchCase
+ ? tag.toLowerCase()
+ : tag))
+ case "route":
+ return taggedOps.mapEntries(([k,v]) => {
+ const newValue = v.set(
+ "operations",
+ v.get("operations")
+ .filter(op => isMatch(!filterConfig.matchCase
+ ? op.get("path").toLowerCase()
+ : op.get("path")))
+ )
+ return [k, newValue]
+ }).filter((tagObj) => tagObj.get("operations").size !== 0)
+ }
}
diff --git a/src/core/plugins/layout/actions.js b/src/core/plugins/layout/actions.js
index 987395b0662..c575ddd47f1 100644
--- a/src/core/plugins/layout/actions.js
+++ b/src/core/plugins/layout/actions.js
@@ -2,6 +2,7 @@ import { normalizeArray } from "core/utils"
export const UPDATE_LAYOUT = "layout_update_layout"
export const UPDATE_FILTER = "layout_update_filter"
+export const UPDATE_FILTER_CONFIG = "layout_update_filter_config"
export const UPDATE_MODE = "layout_update_mode"
export const SHOW = "layout_show"
@@ -21,6 +22,13 @@ export function updateFilter(filter) {
}
}
+export function updateFilterConfig(filterConfig) {
+ return {
+ type: UPDATE_FILTER_CONFIG,
+ payload: filterConfig
+ }
+}
+
export function show(thing, shown=true) {
thing = normalizeArray(thing)
return {
diff --git a/src/core/plugins/layout/reducers.js b/src/core/plugins/layout/reducers.js
index aa067abdfd0..b005720c62a 100644
--- a/src/core/plugins/layout/reducers.js
+++ b/src/core/plugins/layout/reducers.js
@@ -3,7 +3,8 @@ import {
UPDATE_LAYOUT,
UPDATE_FILTER,
UPDATE_MODE,
- SHOW
+ SHOW,
+ UPDATE_FILTER_CONFIG,
} from "./actions"
export default {
@@ -12,6 +13,8 @@ export default {
[UPDATE_FILTER]: (state, action) => state.set("filter", action.payload),
+ [UPDATE_FILTER_CONFIG]: (state, action) => state.set("filterConfig", action.payload),
+
[SHOW]: (state, action) => {
const isShown = action.payload.shown
// This is one way to serialize an array, another (preferred) is to convert to json-pointer
@@ -27,6 +30,6 @@ export default {
let thing = action.payload.thing
let mode = action.payload.mode
return state.setIn(["modes"].concat(thing), (mode || "") + "")
- }
+ },
}
diff --git a/src/core/plugins/layout/selectors.js b/src/core/plugins/layout/selectors.js
index 7d75d37dc88..ecf60b287fa 100644
--- a/src/core/plugins/layout/selectors.js
+++ b/src/core/plugins/layout/selectors.js
@@ -7,6 +7,7 @@ const state = state => state
export const current = state => state.get("layout")
export const currentFilter = state => state.get("filter")
+export const currentFilterConfig = state => state.get("filterConfig")
export const isShown = (state, thing, def) => {
thing = normalizeArray(thing)
diff --git a/src/style/_layout.scss b/src/style/_layout.scss
index 91493496fee..4e94fa6b3e9 100644
--- a/src/style/_layout.scss
+++ b/src/style/_layout.scss
@@ -417,12 +417,23 @@
{
.operation-filter-input
{
- width: 100%;
margin: 20px 0;
padding: 10px 10px;
-
border: 2px solid $operational-filter-input-border-color;
}
+
+ [contenteditable][placeholder]:empty:before {
+ content: attr(placeholder);
+ color: gray;
+ background-color: transparent;
+ }
+
+ .location-select {
+ border: none;
+ outline: none;
+ background-color: transparent;
+ box-shadow: 0 1px 2px rgba(0,0,0,.1);
+ }
}
.filter, .download-url-wrapper
diff --git a/test/e2e-cypress/tests/bugs/6276.js b/test/e2e-cypress/tests/bugs/6276.js
index e26dbd72b69..a04189427b3 100644
--- a/test/e2e-cypress/tests/bugs/6276.js
+++ b/test/e2e-cypress/tests/bugs/6276.js
@@ -31,7 +31,7 @@ describe("#6276: Query parameter filter=true is filtering by the value 'true'",
cy.visit("/?url=/documents/petstore.swagger.yaml&filter=pet")
.get(".operation-filter-input")
.should("exist")
- .should("have.value", "pet")
+ .should("have.text", "pet")
.get(".opblock-tag[data-tag='pet']")
.should("exist")
.get(".opblock-tag[data-tag='store']")
diff --git a/test/unit/components/filter.jsx b/test/unit/components/filter.jsx
index 6be672df3b3..a0a47435a20 100644
--- a/test/unit/components/filter.jsx
+++ b/test/unit/components/filter.jsx
@@ -10,7 +10,8 @@ describe("
", function(){
loadingStatus() {}
},
layoutSelectors: {
- currentFilter() {}
+ currentFilter() {},
+ currentFilterConfig() {}
},
getComponent: () => {return Col}
}
@@ -21,6 +22,11 @@ describe("
", function(){
let props = {...mockedProps}
props.layoutSelectors = {...mockedProps.specSelectors}
props.layoutSelectors.currentFilter = function() {return true}
+ props.layoutSelectors.currentFilterConfig = function() {return {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ }}
// When
let wrapper = mount(
)
@@ -36,6 +42,11 @@ describe("
", function(){
let props = {...mockedProps}
props.layoutSelectors = {...mockedProps.specSelectors}
props.layoutSelectors.currentFilter = function() {return null}
+ props.layoutSelectors.currentFilterConfig = function() {return {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ }}
// When
let wrapper = mount(
)
@@ -51,6 +62,11 @@ describe("
", function(){
let props = {...mockedProps}
props.layoutSelectors = {...mockedProps.specSelectors}
props.layoutSelectors.currentFilter = function() {return false}
+ props.layoutSelectors.currentFilterConfig = function() {return {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ }}
// When
let wrapper = mount(
)
diff --git a/test/unit/components/operations.jsx b/test/unit/components/operations.jsx
index d8295aa332f..b6c5be8d9fc 100644
--- a/test/unit/components/operations.jsx
+++ b/test/unit/components/operations.jsx
@@ -53,6 +53,13 @@ describe("
", function(){
currentFilter() {
return null
},
+ currentFilterConfig() {
+ return {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ }
+ },
isShown() {
return true
},
@@ -108,6 +115,13 @@ describe("
", function(){
currentFilter() {
return null
},
+ currentFilterConfig() {
+ return {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: false,
+ }
+ },
isShown() {
return true
},
diff --git a/test/unit/core/plugins/filter/opsFilter.js b/test/unit/core/plugins/filter/opsFilter.js
index d64ce609ba4..f7adb2f9be3 100644
--- a/test/unit/core/plugins/filter/opsFilter.js
+++ b/test/unit/core/plugins/filter/opsFilter.js
@@ -1,24 +1,162 @@
import { Map } from "immutable"
import opsFilter from "corePlugins/filter/opsFilter"
-describe("opsFilter", function() {
- const taggedOps = Map([["pet"], ["store"], ["user"]])
+describe("opsFilter", () => {
+ const taggedOps = Map([["pet"], ["store"], ["user"], ["user word"]])
- it("should filter taggedOps by tag name", function () {
- const filtered = opsFilter(taggedOps, "sto")
+ describe("with default filterConfig", () => {
+ it("should filter taggedOps by tag name", () => {
+ const filtered = opsFilter(taggedOps, "sto")
- expect(filtered.size).toEqual(1)
+ expect(filtered.size).toEqual(1)
+ })
+
+ it("should filter taggedOps using case sensitive matching", () => {
+ const filtered = opsFilter(taggedOps, "Sto")
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should return all taggedOps when search phrase is empty", () => {
+ const filtered = opsFilter(taggedOps, "")
+
+ expect(filtered.size).toEqual(taggedOps.size)
+ })
+
+ it("should return empty result when there is no match", () => {
+ const filtered = opsFilter(taggedOps, "NoMatch")
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should not use regex matching", () => {
+ const filtered = opsFilter(taggedOps, ".*")
+
+ expect(filtered.size).toEqual(0)
+ })
})
- it("should return all taggedOps when search phrase is empty", function () {
- const filtered = opsFilter(taggedOps, "")
+ describe("with regex matching", () => {
+ const regexFilterConfig = {
+ isRegexFilter: true,
+ matchCase: true,
+ matchWords: false,
+ }
+ it("should filter taggedOps by tag name", () => {
+ const filtered = opsFilter(taggedOps, "st.", regexFilterConfig)
+
+ expect(filtered.size).toEqual(1)
+ })
+
+ it("should filter taggedOps using case sensitive matching", () => {
+ const filtered = opsFilter(taggedOps, "St.", regexFilterConfig)
- expect(filtered.size).toEqual(taggedOps.size)
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should return all taggedOps when search phrase is empty", () => {
+ const filtered = opsFilter(taggedOps, "", regexFilterConfig)
+
+ expect(filtered.size).toEqual(taggedOps.size)
+ })
+
+ it("should return empty result when there is no match", () => {
+ const filtered = opsFilter(taggedOps, "NoM.tch", regexFilterConfig)
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should use regex matching", () => {
+ const filtered = opsFilter(taggedOps, ".*", regexFilterConfig)
+
+ expect(filtered.size).toEqual(taggedOps.size)
+ })
})
- it("should return empty result when there is no match", function () {
- const filtered = opsFilter(taggedOps, "NoMatch")
+ describe("full words matching", () => {
+ const filterConfig = {
+ isRegexFilter: false,
+ matchCase: true,
+ matchWords: true,
+ }
+ const regexFilterConfig = {
+ isRegexFilter: true,
+ matchCase: true,
+ matchWords: true,
+ }
+ describe("with default matching", () => {
+ it("should return empty result if it is a partial match", () => {
+ const filtered = opsFilter(taggedOps, "sto", filterConfig)
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should return full words match result, when string boundary == word boundary", () => {
+ const filtered = opsFilter(taggedOps, "store", filterConfig)
+
+ expect(filtered.size).toEqual(1)
+ })
+
+ it("should return full words match result, when space == word boundary", () => {
+ const filtered = opsFilter(taggedOps, "user", filterConfig)
+
+ expect(filtered.size).toEqual(2)
+ })
+
+ it("should filter taggedOps using case sensitive matching", () => {
+ const filtered = opsFilter(taggedOps, "Store", filterConfig)
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should return all taggedOps when search phrase is empty", () => {
+ const filtered = opsFilter(taggedOps, "", filterConfig)
+
+ expect(filtered.size).toEqual(taggedOps.size)
+ })
+
+ it("should return empty result when there is no match", () => {
+ const filtered = opsFilter(taggedOps, "NoMatch", filterConfig)
+
+ expect(filtered.size).toEqual(0)
+ })
+ })
+ describe("with regex matching", () => {
+ it("should return empty result if it is a partial match", () => {
+ const filtered = opsFilter(taggedOps, "st.", regexFilterConfig)
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should return full words match result, when string boundary == word boundary", () => {
+ const filtered = opsFilter(taggedOps, "st.re", regexFilterConfig)
+
+ expect(filtered.size).toEqual(1)
+ })
+
+ it("should return full words match result, when space == word boundary", () => {
+ const filtered = opsFilter(taggedOps, "u.er", regexFilterConfig)
+
+ expect(filtered.size).toEqual(2)
+ })
+
+
+ it("should filter taggedOps using case sensitive matching", () => {
+ const filtered = opsFilter(taggedOps, "St.re", regexFilterConfig)
+
+ expect(filtered.size).toEqual(0)
+ })
+
+ it("should return all taggedOps when search phrase is empty", () => {
+ const filtered = opsFilter(taggedOps, "", regexFilterConfig)
+
+ expect(filtered.size).toEqual(taggedOps.size)
+ })
+ it("should return empty result when there is no match", () => {
+ const filtered = opsFilter(taggedOps, "NoM.tch", filterConfig)
- expect(filtered.size).toEqual(0)
+ expect(filtered.size).toEqual(0)
+ })
+ })
})
})