local F=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local K=require(F.DevPackages.JestGlobals)local d=require(F.Packages.Lyra.JsonPatch)local J=require(F.Packages.Lyra.Tables)local N=K.describe local P=K.it local M=K.expect local j={{comment="\065\046\049\046  \065\100\100\105\110\103 \097\110 \079\098\106\101\099\116 \077\101\109\098\101\114",doc={foo="\098\097\114"};patch={{op="\097\100\100",path="\047\098\097\122";value="\113\117\120"}},expected={baz="\113\117\120",foo="\098\097\114"}};{comment="\065\046\050\046  \065\100\100\105\110\103 \097\110 \065\114\114\097\121 \069\108\101\109\101\110\116";doc={foo={"\098\097\114";"\098\097\122"}},patch={{op="\097\100\100",path="\047\102\111\111\047\049";value="\113\117\120"}};expected={foo={"\098\097\114";"\113\117\120","\098\097\122"}}};{comment="\065\046\051\046  \082\101\109\111\118\105\110\103 \097\110 \079\098\106\101\099\116 \077\101\109\098\101\114";doc={baz="\113\117\120";foo="\098\097\114"},patch={{op="\114\101\109\111\118\101";path="\047\098\097\122"}};expected={foo="\098\097\114"}};{comment="\065\046\052\046  \082\101\109\111\118\105\110\103 \097\110 \065\114\114\097\121 \069\108\101\109\101\110\116";doc={foo={"\098\097\114";"\113\117\120";"\098\097\122"}};patch={{op="\114\101\109\111\118\101",path="\047\102\111\111\047\049"}},expected={foo={"\098\097\114";"\098\097\122"}}};{comment="\065\046\053\046  \082\101\112\108\097\099\105\110\103 \097 \086\097\108\117\101",doc={baz="\113\117\120",foo="\098\097\114"},patch={{op="\114\101\112\108\097\099\101",path="\047\098\097\122";value="\098\111\111"}},expected={baz="\098\111\111",foo="\098\097\114"}};{comment="\065\046\049\048\046 \065\100\100\105\110\103 \097 \110\101\115\116\101\100 \077\101\109\098\101\114 \079\098\106\101\099\116";doc={foo="\098\097\114"};patch={{op="\097\100\100";path="\047\099\104\105\108\100";value={grandchild={}}}},expected={foo="\098\097\114",child={grandchild={}}}};{comment="\065\046\049\049\046 \073\103\110\111\114\105\110\103 \085\110\114\101\099\111\103\110\105\122\101\100 \069\108\101\109\101\110\116\115",doc={foo="\098\097\114"},patch={{op="\097\100\100";path="\047\098\097\122",value="\113\117\120",xyz=123}};expected={foo="\098\097\114",baz="\113\117\120"}};{comment="\065\046\049\050\046 \065\100\100\105\110\103 \116\111 \097 \078\111\110-\101\120\105\115\116\101\110\116 \084\097\114\103\101\116",doc={foo="\098\097\114"},patch={{op="\097\100\100";path="\047\098\097\122\047\098\097\116",value="\113\117\120"}};error="\097\100\100 \116\111 \097 \110\111\110-\101\120\105\115\116\101\110\116 \116\097\114\103\101\116"},{comment="\065\046\049\052\046 ~ \069\115\099\097\112\101 \079\114\100\101\114\105\110\103",doc={["\047"]=9;["~\049"]=10};patch={{op="\097\100\100";path="\047~\048\048\049",value=11}},expected={["\047"]=9;["~\049"]=10;["~\048\049"]=11}};{comment="\065\046\049\054\046 \065\100\100\105\110\103 \097\110 \065\114\114\097\121 \086\097\108\117\101";doc={foo={"\098\097\114"}};patch={{op="\097\100\100",path="\047\102\111\111\047-";value={"\097\098\099";"\100\101\102"}}},expected={foo={"\098\097\114";{"\097\098\099";"\100\101\102"}}}}}local W={{comment="\101\109\112\116\121 \108\105\115\116\044 \101\109\112\116\121 \100\111\099\115",doc={};patch={},expected={}},{comment="\101\109\112\116\121 \112\097\116\099\104 \108\105\115\116";doc={foo=1},patch={};expected={foo=1}};{comment="\116\111\112\108\101\118\101\108 \097\114\114\097\121";doc={};patch={{op="\097\100\100",path="\047-",value="\102\111\111"}},expected={"\102\111\111"}};{comment="\116\111\112\108\101\118\101\108 \097\114\114\097\121\044 \110\111 \099\104\097\110\103\101",doc={"\102\111\111"},patch={};expected={"\102\111\111"}},{comment="\116\111\112\108\101\118\101\108 \111\098\106\101\099\116\044 \110\117\109\101\114\105\099 \115\116\114\105\110\103";doc={};patch={{op="\097\100\100",path="\047\102\111\111";value="\049"}},expected={foo="\049"}};{comment="\116\111\112\108\101\118\101\108 \111\098\106\101\099\116\044 \105\110\116\101\103\101\114";doc={},patch={{op="\097\100\100",path="\047\102\111\111",value=1}};expected={foo=1}},{comment="\065\100\100\044 \047 \116\097\114\103\101\116";doc={};patch={{op="\097\100\100";path="\047";value=1}},expected={[""]=1}};{comment="\065\100\100 \099\111\109\112\111\115\105\116\101 \118\097\108\117\101 \097\116 \116\111\112 \108\101\118\101\108";doc={foo=1},patch={{op="\097\100\100",path="\047\098\097\114",value={1,2}}},expected={foo=1,bar={1;2}}},{comment="\065\100\100 \105\110\116\111 \099\111\109\112\111\115\105\116\101 \118\097\108\117\101";doc={foo=1,baz={{qux="\104\101\108\108\111"}}};patch={{op="\097\100\100",path="\047\098\097\122\047\048\047\102\111\111";value="\119\111\114\108\100"}},expected={foo=1,baz={{qux="\104\101\108\108\111",foo="\119\111\114\108\100"}}}};{comment="\065\100\100 \111\117\116 \111\102 \117\112\112\101\114 \098\111\117\110\100\115 \116\104\114\111\119\115";doc={bar={1,2}};patch={{op="\097\100\100";path="\047\098\097\114\047\056",value="\053"}};error="\065\114\114\097\121 \097\100\100 \105\110\100\101\120 \056 \111\117\116 \111\102 \098\111\117\110\100\115 \102\111\114 \108\101\110\103\116\104 \050"},{comment="\065\100\100 \111\117\116 \111\102 \108\111\119\101\114 \098\111\117\110\100\115 \116\104\114\111\119\115",doc={bar={1,2}};patch={{op="\097\100\100";path="\047\098\097\114\047-\049",value="\053"}};error="\065\114\114\097\121 \097\100\100 \105\110\100\101\120 -\049 \111\117\116 \111\102 \098\111\117\110\100\115 \102\111\114 \108\101\110\103\116\104 \050"},{comment="\065\100\100 \116\111 \111\098\106\101\099\116";doc={foo=1};patch={{op="\097\100\100";path="\047\098\097\114",value=true}},expected={foo=1,bar=true}},{comment="\065\100\100 \116\111 \111\098\106\101\099\116";doc={foo=1};patch={{op="\097\100\100",path="\047\098\097\114";value=false}};expected={foo=1;bar=false}},{comment="\048 \099\097\110 \098\101 \097\110 \097\114\114\097\121 \105\110\100\101\120 \111\114 \111\098\106\101\099\116 \101\108\101\109\101\110\116 \110\097\109\101";doc={foo=1},patch={{op="\097\100\100",path="\047\048";value="\098\097\114"}};expected={foo=1;["\048"]="\098\097\114"}};{comment="\065\100\100 \116\111 \109\105\100\100\108\101 \111\102 \097\114\114\097\121";doc={"\102\111\111","\115\105\108"},patch={{op="\097\100\100",path="\047\049",value="\098\097\114"}};expected={"\102\111\111";"\098\097\114","\115\105\108"}},{comment="\065\100\100 \116\111 \098\101\103\105\110\110\105\110\103 \111\102 \097\114\114\097\121";doc={"\102\111\111";"\115\105\108"};patch={{op="\097\100\100",path="\047\048",value="\098\097\114"}},expected={"\098\097\114";"\102\111\111","\115\105\108"}},{comment="\065\100\100 \116\111 \101\110\100 \111\102 \097\114\114\097\121";doc={"\102\111\111";"\115\105\108"};patch={{op="\097\100\100";path="\047-";value="\098\097\114"}},expected={"\102\111\111";"\115\105\108","\098\097\114"}};{comment="\079\098\106\101\099\116 \111\112\101\114\097\116\105\111\110 \111\110 \097\114\114\097\121 \116\104\114\111\119\115",doc={"\102\111\111","\115\105\108"};patch={{op="\097\100\100";path="\047\098\097\114",value=42}},error="\079\098\106\101\099\116 \111\112\101\114\097\116\105\111\110 \111\110 \097\114\114\097\121 \116\097\114\103\101\116"},{comment="\118\097\108\117\101 \105\110 \097\114\114\097\121 \097\100\100 \110\111\116 \102\108\097\116\116\101\110\101\100";doc={"\102\111\111","\115\105\108"};patch={{op="\097\100\100";path="\047\049",value={"\098\097\114","\098\097\122"}}};expected={"\102\111\111";{"\098\097\114","\098\097\122"},"\115\105\108"}};{comment="\114\101\109\111\118\101 \109\101\109\098\101\114 \102\114\111\109 \111\098\106\101\099\116";doc={foo=1,bar={1;2,3;4}};patch={{op="\114\101\109\111\118\101",path="\047\098\097\114"}};expected={foo=1}};{comment="\114\101\109\111\118\101 \100\101\101\112\108\121 \110\101\115\116\101\100 \105\116\101\109",doc={foo=1;baz={{qux="\104\101\108\108\111"}}},patch={{op="\114\101\109\111\118\101";path="\047\098\097\122\047\048\047\113\117\120"}};expected={foo=1;baz={{}}}};{comment="\114\101\112\108\097\099\101 \109\101\109\098\101\114 \105\110 \111\098\106\101\099\116",doc={foo=1;baz={{qux="\104\101\108\108\111"}}};patch={{op="\114\101\112\108\097\099\101",path="\047\102\111\111";value={1;2;3;4}}},expected={foo={1;2;3;4},baz={{qux="\104\101\108\108\111"}}}};{comment="\114\101\112\108\097\099\101 \100\101\101\112\108\121 \110\101\115\116\101\100 \105\116\101\109";doc={foo={1;2;3,4},baz={{qux="\104\101\108\108\111"}}};patch={{op="\114\101\112\108\097\099\101",path="\047\098\097\122\047\048\047\113\117\120";value="\119\111\114\108\100"}};expected={foo={1,2;3;4};baz={{qux="\119\111\114\108\100"}}}};{comment="\114\101\112\108\097\099\101 \105\110 \097\114\114\097\121",doc={"\102\111\111"},patch={{op="\114\101\112\108\097\099\101",path="\047\048";value="\098\097\114"}},expected={"\098\097\114"}};{comment="\114\101\112\108\097\099\101 \105\110 \097\114\114\097\121 \119\105\116\104 \110\117\109\098\101\114";doc={""},patch={{op="\114\101\112\108\097\099\101";path="\047\048",value=0}};expected={0}};{comment="\114\101\112\108\097\099\101 \119\105\116\104 \116\114\117\101";doc={""};patch={{op="\114\101\112\108\097\099\101",path="\047\048",value=true}};expected={true}};{comment="\114\101\112\108\097\099\101 \119\105\116\104 \102\097\108\115\101";doc={""};patch={{op="\114\101\112\108\097\099\101",path="\047\048";value=false}};expected={false}},{doc={"\102\111\111","\115\105\108"};patch={{op="\114\101\112\108\097\099\101",path="\047\049";value={"\098\097\114";"\098\097\122"}}},expected={"\102\111\111";{"\098\097\114","\098\097\122"}},comment="\118\097\108\117\101 \105\110 \097\114\114\097\121 \114\101\112\108\097\099\101 \110\111\116 \102\108\097\116\116\101\110\101\100"},{comment="\114\101\112\108\097\099\101 \119\104\111\108\101 \100\111\099\117\109\101\110\116";doc={foo="\098\097\114"},patch={{op="\114\101\112\108\097\099\101",path="",value={baz="\113\117\120"}}};expected={baz="\113\117\120"}};{comment="\114\101\112\108\097\099\105\110\103 \116\104\101 \114\111\111\116 \111\102 \116\104\101 \100\111\099\117\109\101\110\116 \105\115 \112\111\115\115\105\098\108\101 \119\105\116\104 \097\100\100",doc={foo="\098\097\114"};patch={{op="\097\100\100";path="",value={baz="\113\117\120"}}},expected={baz="\113\117\120"}},{comment="\065\100\100\105\110\103 \116\111 \"\047-\" \097\100\100\115 \116\111 \116\104\101 \101\110\100 \111\102 \116\104\101 \097\114\114\097\121",doc={1;2},patch={{op="\097\100\100",path="\047-",value={foo={"\098\097\114","\098\097\122"}}}};expected={1,2;{foo={"\098\097\114";"\098\097\122"}}}},{comment="\065\100\100\105\110\103 \116\111 \"\047-\" \097\100\100\115 \116\111 \116\104\101 \101\110\100 \111\102 \116\104\101 \097\114\114\097\121\044 \101\118\101\110 \110 \108\101\118\101\108\115 \100\111\119\110",doc={1,2,{3;{4,5}}};patch={{op="\097\100\100",path="\047\050\047\049\047-";value={foo={"\098\097\114";"\098\097\122"}}}},expected={1,2,{3,{4,5,{foo={"\098\097\114";"\098\097\122"}}}}}};{comment="\116\101\115\116 \114\101\109\111\118\101 \119\105\116\104 \098\097\100 \110\117\109\098\101\114 \115\104\111\117\108\100 \102\097\105\108",doc={foo=1;baz={{qux="\104\101\108\108\111"}}};patch={{op="\114\101\109\111\118\101",path="\047\098\097\122\047\049\101\048\047\113\117\120"}};error="\080\097\116\104 \115\101\103\109\101\110\116 \'\049\101\048\' \105\115 \110\111\116 \097 \118\097\108\105\100 \097\114\114\097\121 \105\110\100\101\120"},{comment="\116\101\115\116 \114\101\109\111\118\101 \111\110 \097\114\114\097\121",doc={1;2;3,4};patch={{op="\114\101\109\111\118\101",path="\047\048"}};expected={2,3;4}};{comment="\116\101\115\116 \114\101\112\101\097\116\101\100 \114\101\109\111\118\101\115",doc={1;2;3,4},patch={{op="\114\101\109\111\118\101",path="\047\049"},{op="\114\101\109\111\118\101";path="\047\050"}},expected={1,3}},{comment="\116\101\115\116 \114\101\109\111\118\101 \119\105\116\104 \098\097\100 \105\110\100\101\120 \115\104\111\117\108\100 \102\097\105\108";doc={1;2;3;4},patch={{op="\114\101\109\111\118\101",path="\047\049\101\048"}};error="\114\101\109\111\118\101 \111\112 \115\104\111\117\108\100\110\'\116 \114\101\109\111\118\101 \102\114\111\109 \097\114\114\097\121 \119\105\116\104 \098\097\100 \110\117\109\098\101\114"},{comment="\116\101\115\116 \114\101\112\108\097\099\101 \119\105\116\104 \098\097\100 \110\117\109\098\101\114 \115\104\111\117\108\100 \102\097\105\108";doc={""};patch={{op="\114\101\112\108\097\099\101",path="\047\049\101\048",value=false}};error="\114\101\112\108\097\099\101 \111\112 \115\104\111\117\108\100\110\'\116 \114\101\112\108\097\099\101 \105\110 \097\114\114\097\121 \119\105\116\104 \098\097\100 \110\117\109\098\101\114"};{comment="\116\101\115\116 \097\100\100 \119\105\116\104 \098\097\100 \110\117\109\098\101\114 \115\104\111\117\108\100 \102\097\105\108",doc={"\102\111\111","\115\105\108"};patch={{op="\097\100\100";path="\047\049\101\048",value="\098\097\114"}};error="\097\100\100 \111\112 \115\104\111\117\108\100\110\'\116 \097\100\100 \116\111 \097\114\114\097\121 \119\105\116\104 \098\097\100 \110\117\109\098\101\114"},{comment="\109\105\115\115\105\110\103 \'\118\097\108\117\101\' \112\097\114\097\109\101\116\101\114 \116\111 \097\100\100",doc={1};patch={{op="\097\100\100";path="\047-"}},error="\109\105\115\115\105\110\103 \'\118\097\108\117\101\' \112\097\114\097\109\101\116\101\114"};{comment="\109\105\115\115\105\110\103 \'\118\097\108\117\101\' \112\097\114\097\109\101\116\101\114 \116\111 \114\101\112\108\097\099\101";doc={1};patch={{op="\114\101\112\108\097\099\101";path="\047\048"}};error="\109\105\115\115\105\110\103 \'\118\097\108\117\101\' \112\097\114\097\109\101\116\101\114"},{comment="\117\110\114\101\099\111\103\110\105\122\101\100 \111\112 \115\104\111\117\108\100 \102\097\105\108",doc={foo=1};patch={{op="\115\112\097\109",path="\047\102\111\111",value=1}},error="\085\110\114\101\099\111\103\110\105\122\101\100 \111\112 \'\115\112\097\109\'"},{comment="\097\100\100 \116\111 \097\109\098\105\103\117\111\117\115 \111\098\106\101\099\116";doc={["\049"]="\102\111\111";["\048\049"]="\098\097\114"},patch={{op="\097\100\100",path="\047\049",value="\098\097\122"}};expected={["\049"]="\098\097\122";["\048\049"]="\098\097\114"}};{comment="\097\112\112\101\110\100 \116\111 \097\109\098\105\103\117\111\117\115 \111\098\106\101\099\116",doc={["\049"]="\102\111\111";["\048\049"]="\098\097\114"},patch={{op="\097\100\100";path="\047-";value="\098\097\122"}};expected={["\049"]="\102\111\111",["\048\049"]="\098\097\114",["-"]="\098\097\122"}};{comment="\097\100\100 \116\111 \097\109\098\105\103\117\111\117\115 \100\101\101\112 \111\098\106\101\099\116";doc={["\049"]={["\050"]="\102\111\111"},["\048\049"]="\098\097\114"},patch={{op="\097\100\100";path="\047\049\047\050",value="\098\097\122"}},expected={["\049"]={["\050"]="\098\097\122"};["\048\049"]="\098\097\114"}};{comment="\097\112\112\101\110\100 \116\111 \097\109\098\105\103\117\111\117\115 \100\101\101\112 \111\098\106\101\099\116",doc={["\049"]={["\050"]="\102\111\111"},["\048\049"]="\098\097\114"},patch={{op="\097\100\100";path="\047\049\047-",value="\098\097\122"}},expected={["\049"]={["\050"]="\102\111\111",["-"]="\098\097\122"};["\048\049"]="\098\097\114"}};{comment="\097\100\100 \116\111 \097\109\098\105\103\117\111\117\115 \100\101\101\112 \097\114\114\097\121",doc={["-"]={"\102\111\111"};["\048\049"]="\098\097\114"},patch={{op="\097\100\100",path="\047-\047-";value="\098\097\122"}},expected={["-"]={"\102\111\111";"\098\097\122"};["\048\049"]="\098\097\114"}}}N("\074\115\111\110\080\097\116\099\104",function()N("\083\112\101\099 \116\101\115\116\115",function()(P.each(j))("\036\099\111\109\109\101\110\116",function(F)if F.disabled then return end if F.error then(M(function()d.applyPatch(F.doc,F.patch)end)).toThrow(F.error)else local K=J.copyDeep(F.doc)local N=d.applyPatch(K,F.patch);(M(N)).toEqual(F.expected)K=J.copyDeep(F.doc)local P=d.createPatch(K,F.expected)N=d.applyPatch(K,P);(M(N)).toEqual(F.expected)end end)end)N("\084\101\115\116 \099\097\115\101\115",function()(P.each(W))("\036\099\111\109\109\101\110\116",function(F)if F.disabled then return end if F.error then(M(function()d.applyPatch(F.doc,F.patch)end)).toThrow(F.error)else local K=J.copyDeep(F.doc)local N=d.applyPatch(K,F.patch);(M(N)).toEqual(F.expected)K=J.copyDeep(F.doc)local P=d.createPatch(K,F.expected)N=d.applyPatch(K,P);(M(N)).toEqual(F.expected)end end)end)N("\082\101\103\114\101\115\115\105\111\110",function()P("\065\114\114\097\121 \114\101\111\114\100\101\114\105\110\103 \049",function()local F={[1]="\097"}local K={[1]="\098",[2]="\099"}local J=d.createPatch(F,K)local N=d.applyPatch(F,J);(M(N)).toEqual(K)end)P("\065\114\114\097\121 \114\101\111\114\100\101\114\105\110\103 \050",function()local F={[1]={foo="\097"}}local K={[1]={bar="\098"}}local J=d.createPatch(F,K)local N=d.applyPatch(F,J);(M(N)).toEqual(K)end)P("\065\114\114\097\121 \114\101\111\114\100\101\114\105\110\103 \051",function()local F={[1]={ROQB={[1]="\122\053\071\067\072\105\111",[2]="\052\088\108\065\115\104\097\079\102",[3]="\083";[4]="\052\107\087\106\080\120\114\082\084"}}}local K={[1]={["\050"]="\106\074\107\115\055\116\119",D={[1]=true;[2]=false,[3]=true;[4]=-922}}}local J=d.createPatch(F,K)local N=d.applyPatch(F,J);(M(N)).toEqual(K)end)P("\065\114\114\097\121 \114\101\111\114\100\101\114\105\110\103 \052",function()local F={[1]=1}local K={[1]=1,[2]=2,[3]=3;[4]=4}local J=d.createPatch(F,K)local N=d.applyPatch(F,J);(M(N)).toEqual(K)end)end)end)
--[[
	Based on https://github.com/Starcounter-Jack/JSON-Patch

	(The MIT License)

	Copyright (c) 2013, 2014, 2020 Joachim Wester

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	'Software'), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local JestGlobals = require(ReplicatedStorage.DevPackages.JestGlobals)
local JsonPatch = require(ReplicatedStorage.Packages.Lyra.JsonPatch)
local Tables = require(ReplicatedStorage.Packages.Lyra.Tables)
local describe = JestGlobals.describe
local it = JestGlobals.it
local expect = JestGlobals.expect

local SPEC_TESTS = {
	{
		comment = "A.1.  Adding an Object Member",
		doc = { foo = "bar" },
		patch = { { op = "add", path = "/baz", value = "qux" } },
		expected = {
			baz = "qux",
			foo = "bar",
		},
	},
	{
		comment = "A.2.  Adding an Array Element",
		doc = {
			foo = { "bar", "baz" },
		},
		patch = { { op = "add", path = "/foo/1", value = "qux" } },
		expected = {
			foo = { "bar", "qux", "baz" },
		},
	},
	{
		comment = "A.3.  Removing an Object Member",
		doc = {
			baz = "qux",
			foo = "bar",
		},
		patch = { { op = "remove", path = "/baz" } },
		expected = {
			foo = "bar",
		},
	},
	{
		comment = "A.4.  Removing an Array Element",
		doc = {
			foo = { "bar", "qux", "baz" },
		},
		patch = { { op = "remove", path = "/foo/1" } },
		expected = {
			foo = { "bar", "baz" },
		},
	},
	{
		comment = "A.5.  Replacing a Value",
		doc = {
			baz = "qux",
			foo = "bar",
		},
		patch = { { op = "replace", path = "/baz", value = "boo" } },
		expected = {
			baz = "boo",
			foo = "bar",
		},
	},
	-- A.6 omitted - move not implemented
	-- A.7 omitted - move not implemented
	-- A.8 omitted - test not implemented
	-- A.9 omitted - test not implemented
	{
		comment = "A.10. Adding a nested Member Object",
		doc = {
			foo = "bar",
		},
		patch = { { op = "add", path = "/child", value = { grandchild = {} } } },
		expected = {
			foo = "bar",
			child = {
				grandchild = {},
			},
		},
	},
	{
		comment = "A.11. Ignoring Unrecognized Elements",
		doc = {
			foo = "bar",
		},
		patch = { { op = "add", path = "/baz", value = "qux", xyz = 123 } },
		expected = {
			foo = "bar",
			baz = "qux",
		},
	},
	{
		comment = "A.12. Adding to a Non-existent Target",
		doc = {
			foo = "bar",
		},
		patch = { { op = "add", path = "/baz/bat", value = "qux" } },
		error = "add to a non-existent target",
	},
	-- A.13 omitted - duplicate keys not possible in Luau
	{
		comment = "A.14. ~ Escape Ordering",
		doc = {
			["/"] = 9,
			["~1"] = 10,
		},
		patch = { { op = "add", path = "/~001", value = 11 } },
		expected = {
			["/"] = 9,
			["~1"] = 10,
			["~01"] = 11,
		},
	},
	-- A.15 omitted - duplicate keys not possible in Luau
	{
		comment = "A.16. Adding an Array Value",
		doc = {
			foo = { "bar" },
		},
		patch = { { op = "add", path = "/foo/-", value = { "abc", "def" } } },
		expected = {
			foo = { "bar", { "abc", "def" } },
		},
	},
}

local APPLY_TESTS = {
	{ comment = "empty list, empty docs", doc = {}, patch = {}, expected = {} },
	{ comment = "empty patch list", doc = { foo = 1 }, patch = {}, expected = { foo = 1 } },
	{
		comment = "toplevel array",
		doc = {},
		patch = { { op = "add", path = "/-", value = "foo" } },
		expected = { "foo" },
	},
	{ comment = "toplevel array, no change", doc = { "foo" }, patch = {}, expected = { "foo" } },
	{
		comment = "toplevel object, numeric string",
		doc = {},
		patch = { { op = "add", path = "/foo", value = "1" } },
		expected = { foo = "1" },
	},
	{
		comment = "toplevel object, integer",
		doc = {},
		patch = { { op = "add", path = "/foo", value = 1 } },
		expected = { foo = 1 },
	},
	{
		comment = "Add, / target",
		doc = {},
		patch = { { op = "add", path = "/", value = 1 } },
		expected = { [""] = 1 },
	},
	{
		comment = "Add composite value at top level",
		doc = { foo = 1 },
		patch = { { op = "add", path = "/bar", value = { 1, 2 } } },
		expected = { foo = 1, bar = { 1, 2 } },
	},
	{
		comment = "Add into composite value",
		doc = { foo = 1, baz = { { qux = "hello" } } },
		patch = { { op = "add", path = "/baz/0/foo", value = "world" } },
		expected = { foo = 1, baz = { { qux = "hello", foo = "world" } } },
	},
	{
		comment = "Add out of upper bounds throws",
		doc = { bar = { 1, 2 } },
		patch = { { op = "add", path = "/bar/8", value = "5" } },
		error = "Array add index 8 out of bounds for length 2",
	},
	{
		comment = "Add out of lower bounds throws",
		doc = { bar = { 1, 2 } },
		patch = { { op = "add", path = "/bar/-1", value = "5" } },
		error = "Array add index -1 out of bounds for length 2",
	},
	{
		comment = "Add to object",
		doc = { foo = 1 },
		patch = { { op = "add", path = "/bar", value = true } },
		expected = { foo = 1, bar = true },
	},
	{
		comment = "Add to object",
		doc = { foo = 1 },
		patch = { { op = "add", path = "/bar", value = false } },
		expected = { foo = 1, bar = false },
	},
	{
		comment = "0 can be an array index or object element name",
		doc = { foo = 1 },
		patch = { { op = "add", path = "/0", value = "bar" } },
		expected = { foo = 1, ["0"] = "bar" },
	},
	{
		comment = "Add to middle of array",
		doc = { "foo", "sil" },
		patch = { { op = "add", path = "/1", value = "bar" } },
		expected = { "foo", "bar", "sil" },
	},
	{
		comment = "Add to beginning of array",
		doc = { "foo", "sil" },
		patch = { { op = "add", path = "/0", value = "bar" } },
		expected = { "bar", "foo", "sil" },
	},
	{
		comment = "Add to end of array",
		doc = { "foo", "sil" },
		patch = { { op = "add", path = "/-", value = "bar" } },
		expected = { "foo", "sil", "bar" },
	},
	{
		comment = "Object operation on array throws",
		doc = { "foo", "sil" },
		patch = { { op = "add", path = "/bar", value = 42 } },
		error = "Object operation on array target",
	},
	{
		comment = "value in array add not flattened",
		doc = { "foo", "sil" },
		patch = { { op = "add", path = "/1", value = { "bar", "baz" } } },
		expected = { "foo", { "bar", "baz" }, "sil" },
	},
	{
		comment = "remove member from object",
		doc = { foo = 1, bar = { 1, 2, 3, 4 } },
		patch = { { op = "remove", path = "/bar" } },
		expected = { foo = 1 },
	},
	{
		comment = "remove deeply nested item",
		doc = { foo = 1, baz = { { qux = "hello" } } },
		patch = { { op = "remove", path = "/baz/0/qux" } },
		expected = { foo = 1, baz = { {} } },
	},
	{
		comment = "replace member in object",
		doc = { foo = 1, baz = { { qux = "hello" } } },
		patch = { { op = "replace", path = "/foo", value = { 1, 2, 3, 4 } } },
		expected = { foo = { 1, 2, 3, 4 }, baz = { { qux = "hello" } } },
	},
	{
		comment = "replace deeply nested item",
		doc = { foo = { 1, 2, 3, 4 }, baz = { { qux = "hello" } } },
		patch = { { op = "replace", path = "/baz/0/qux", value = "world" } },
		expected = { foo = { 1, 2, 3, 4 }, baz = { { qux = "world" } } },
	},
	{
		comment = "replace in array",
		doc = { "foo" },
		patch = { { op = "replace", path = "/0", value = "bar" } },
		expected = { "bar" },
	},
	{
		comment = "replace in array with number",
		doc = { "" },
		patch = { { op = "replace", path = "/0", value = 0 } },
		expected = { 0 },
	},
	{
		comment = "replace with true",
		doc = { "" },
		patch = { { op = "replace", path = "/0", value = true } },
		expected = { true },
	},
	{
		comment = "replace with false",
		doc = { "" },
		patch = { { op = "replace", path = "/0", value = false } },
		expected = { false },
	},
	{
		doc = { "foo", "sil" },
		patch = { { op = "replace", path = "/1", value = { "bar", "baz" } } },
		expected = { "foo", { "bar", "baz" } },
		comment = "value in array replace not flattened",
	},
	{
		comment = "replace whole document",
		doc = { foo = "bar" },
		patch = { { op = "replace", path = "", value = { baz = "qux" } } },
		expected = { baz = "qux" },
	},
	{
		comment = "replacing the root of the document is possible with add",
		doc = { foo = "bar" },
		patch = { { op = "add", path = "", value = { baz = "qux" } } },
		expected = { baz = "qux" },
	},
	{
		comment = 'Adding to "/-" adds to the end of the array',
		doc = { 1, 2 },
		patch = { { op = "add", path = "/-", value = { foo = { "bar", "baz" } } } },
		expected = { 1, 2, { foo = { "bar", "baz" } } },
	},
	{
		comment = 'Adding to "/-" adds to the end of the array, even n levels down',
		doc = { 1, 2, { 3, { 4, 5 } } },
		patch = { { op = "add", path = "/2/1/-", value = { foo = { "bar", "baz" } } } },
		expected = { 1, 2, { 3, { 4, 5, { foo = { "bar", "baz" } } } } },
	},
	{
		comment = "test remove with bad number should fail",
		doc = { foo = 1, baz = { { qux = "hello" } } },
		patch = { { op = "remove", path = "/baz/1e0/qux" } },
		error = "Path segment '1e0' is not a valid array index",
	},
	{
		comment = "test remove on array",
		doc = { 1, 2, 3, 4 },
		patch = { { op = "remove", path = "/0" } },
		expected = { 2, 3, 4 },
	},
	{
		comment = "test repeated removes",
		doc = { 1, 2, 3, 4 },
		patch = { { op = "remove", path = "/1" }, { op = "remove", path = "/2" } },
		expected = { 1, 3 },
	},
	{
		comment = "test remove with bad index should fail",
		doc = { 1, 2, 3, 4 },
		patch = { { op = "remove", path = "/1e0" } },
		error = "remove op shouldn't remove from array with bad number",
	},
	{
		comment = "test replace with bad number should fail",
		doc = { "" },
		patch = { { op = "replace", path = "/1e0", value = false } },
		error = "replace op shouldn't replace in array with bad number",
	},
	{
		comment = "test add with bad number should fail",
		doc = { "foo", "sil" },
		patch = { { op = "add", path = "/1e0", value = "bar" } },
		error = "add op shouldn't add to array with bad number",
	},
	{
		comment = "missing 'value' parameter to add",
		doc = { 1 },
		patch = { { op = "add", path = "/-" } },
		error = "missing 'value' parameter",
	},
	{
		comment = "missing 'value' parameter to replace",
		doc = { 1 },
		patch = { { op = "replace", path = "/0" } },
		error = "missing 'value' parameter",
	},
	{
		comment = "unrecognized op should fail",
		doc = { foo = 1 },
		patch = { { op = "spam", path = "/foo", value = 1 } },
		error = "Unrecognized op 'spam'",
	},
	{
		comment = "add to ambiguous object",
		doc = { ["1"] = "foo", ["01"] = "bar" },
		patch = { { op = "add", path = "/1", value = "baz" } },
		expected = { ["1"] = "baz", ["01"] = "bar" },
	},
	{
		comment = "append to ambiguous object",
		doc = { ["1"] = "foo", ["01"] = "bar" },
		patch = { { op = "add", path = "/-", value = "baz" } },
		expected = { ["1"] = "foo", ["01"] = "bar", ["-"] = "baz" },
	},
	{
		comment = "add to ambiguous deep object",
		doc = { ["1"] = { ["2"] = "foo" }, ["01"] = "bar" },
		patch = { { op = "add", path = "/1/2", value = "baz" } },
		expected = { ["1"] = { ["2"] = "baz" }, ["01"] = "bar" },
	},
	{
		comment = "append to ambiguous deep object",
		doc = { ["1"] = { ["2"] = "foo" }, ["01"] = "bar" },
		patch = { { op = "add", path = "/1/-", value = "baz" } },
		expected = { ["1"] = { ["2"] = "foo", ["-"] = "baz" }, ["01"] = "bar" },
	},
	{
		comment = "add to ambiguous deep array",
		doc = { ["-"] = { "foo" }, ["01"] = "bar" },
		patch = { { op = "add", path = "/-/-", value = "baz" } },
		expected = { ["-"] = { "foo", "baz" }, ["01"] = "bar" },
	},
}

describe("JsonPatch", function()
	describe("Spec tests", function()
		it.each(SPEC_TESTS)("$comment", function(testCase)
			if testCase.disabled then
				return
			end

			if testCase.error then
				expect(function()
					JsonPatch.applyPatch(testCase.doc, testCase.patch)
				end).toThrow(testCase.error)
			else
				local input = Tables.copyDeep(testCase.doc)
				local result = JsonPatch.applyPatch(input, testCase.patch)
				expect(result).toEqual(testCase.expected)

				input = Tables.copyDeep(testCase.doc)
				local generatedPatch = JsonPatch.createPatch(input, testCase.expected)
				result = JsonPatch.applyPatch(input, generatedPatch)
				expect(result).toEqual(testCase.expected)
			end
		end)
	end)

	describe("Test cases", function()
		it.each(APPLY_TESTS)("$comment", function(testCase)
			if testCase.disabled then
				return
			end

			if testCase.error then
				expect(function()
					JsonPatch.applyPatch(testCase.doc, testCase.patch)
				end).toThrow(testCase.error)
			else
				local input = Tables.copyDeep(testCase.doc)
				local result = JsonPatch.applyPatch(input, testCase.patch)
				expect(result).toEqual(testCase.expected)

				input = Tables.copyDeep(testCase.doc)
				local generatedPatch = JsonPatch.createPatch(input, testCase.expected)
				result = JsonPatch.applyPatch(input, generatedPatch)
				expect(result).toEqual(testCase.expected)
			end
		end)
	end)

	describe("Regression", function()
		it("Array reordering 1", function()
			local input = {
				[1] = "a",
			}
			local output = {
				[1] = "b",
				[2] = "c",
			}

			local patch = JsonPatch.createPatch(input, output)
			local result = JsonPatch.applyPatch(input, patch)

			expect(result).toEqual(output)
		end)

		it("Array reordering 2", function()
			local input = {
				[1] = {
					["foo"] = "a",
				},
			}
			local output = {
				[1] = {
					["bar"] = "b",
				},
			}

			local patch = JsonPatch.createPatch(input, output)
			local result = JsonPatch.applyPatch(input, patch)

			expect(result).toEqual(output)
		end)

		it("Array reordering 3", function()
			local input = {
				[1] = {
					["ROQB"] = {
						[1] = "z5GCHio",
						[2] = "4XlAshaOf",
						[3] = "S",
						[4] = "4kWjPxrRT",
					},
				},
			}
			local output = {
				[1] = {
					["2"] = "jJks7tw",
					["D"] = {
						[1] = true,
						[2] = false,
						[3] = true,
						[4] = -922,
					},
				},
			}

			local patch = JsonPatch.createPatch(input, output)
			local result = JsonPatch.applyPatch(input, patch)

			expect(result).toEqual(output)
		end)

		it("Array reordering 4", function()
			local input = {
				[1] = 1,
			}
			local output = {
				[1] = 1,
				[2] = 2,
				[3] = 3,
				[4] = 4,
			}

			local patch = JsonPatch.createPatch(input, output)
			local result = JsonPatch.applyPatch(input, patch)

			expect(result).toEqual(output)
		end)
	end)
end)
