mirror of
https://github.com/gogrlx/grlx-lsp.git
synced 2026-04-02 03:18:47 -07:00
test(lsp): improve handler test coverage from 22% to 68%
- Add completion_test.go: tests for top-level, ingredient.method, property, requisite type, and step ID completions - Add hover_test.go: tests for markdown generation, word extraction, and edge cases - Add diagnostics_test.go: tests for nil recipe, parse errors, empty ingredient, requisite validation, range helpers - Update all dependencies to latest versions - Add target/ to .gitignore (stale Rust build artifacts)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
grlx-lsp
|
grlx-lsp
|
||||||
dist/
|
dist/
|
||||||
|
target/
|
||||||
|
|||||||
11
go.mod
11
go.mod
@@ -9,12 +9,11 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/segmentio/asm v1.1.3 // indirect
|
github.com/segmentio/asm v1.2.1 // indirect
|
||||||
github.com/segmentio/encoding v0.3.4 // indirect
|
github.com/segmentio/encoding v0.5.3 // indirect
|
||||||
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
|
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
|
||||||
go.lsp.dev/uri v0.3.0 // indirect
|
go.lsp.dev/uri v0.3.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.8.0 // indirect
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
go.uber.org/zap v1.21.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
77
go.sum
77
go.sum
@@ -1,29 +1,16 @@
|
|||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc=
|
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
|
||||||
github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM=
|
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=
|
go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=
|
||||||
go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
|
go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
|
||||||
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE=
|
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE=
|
||||||
@@ -32,52 +19,18 @@ go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg=
|
|||||||
go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ=
|
go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ=
|
||||||
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
|
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
|
||||||
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
|
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
|
||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
304
internal/lsp/completion_test.go
Normal file
304
internal/lsp/completion_test.go
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.lsp.dev/protocol"
|
||||||
|
|
||||||
|
"github.com/gogrlx/grlx-lsp/internal/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompleteTopLevel(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := ``
|
||||||
|
h.updateDocument("file:///test.grlx", src)
|
||||||
|
|
||||||
|
items := h.completeTopLevel("")
|
||||||
|
if len(items) != len(schema.TopLevelKeys) {
|
||||||
|
t.Errorf("expected %d top-level items, got %d", len(schema.TopLevelKeys), len(items))
|
||||||
|
}
|
||||||
|
labelSet := make(map[string]bool)
|
||||||
|
for _, item := range items {
|
||||||
|
labelSet[item.Label] = true
|
||||||
|
}
|
||||||
|
for _, key := range schema.TopLevelKeys {
|
||||||
|
if !labelSet[key] {
|
||||||
|
t.Errorf("missing top-level completion: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteIngredientMethod(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
|
||||||
|
// Without dot prefix — should return all ingredient.method combos
|
||||||
|
items := h.completeIngredientMethod("")
|
||||||
|
if len(items) == 0 {
|
||||||
|
t.Fatal("expected completion items for all ingredient.method combos")
|
||||||
|
}
|
||||||
|
labelSet := make(map[string]bool)
|
||||||
|
for _, item := range items {
|
||||||
|
labelSet[item.Label] = true
|
||||||
|
}
|
||||||
|
for _, name := range []string{"file.managed", "cmd.run", "pkg.installed", "service.running"} {
|
||||||
|
if !labelSet[name] {
|
||||||
|
t.Errorf("missing completion: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With dot prefix — should complete methods for that ingredient
|
||||||
|
items = h.completeIngredientMethod("file.")
|
||||||
|
if len(items) == 0 {
|
||||||
|
t.Fatal("expected method completions for file ingredient")
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Kind != protocol.CompletionItemKindFunction {
|
||||||
|
t.Errorf("expected Function kind, got %v", item.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown ingredient with dot
|
||||||
|
items = h.completeIngredientMethod("bogus.")
|
||||||
|
if len(items) != 0 {
|
||||||
|
t.Errorf("expected no completions for unknown ingredient, got %d", len(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteProperties(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:
|
||||||
|
install nginx:
|
||||||
|
pkg.installed:
|
||||||
|
- name: nginx
|
||||||
|
- `
|
||||||
|
h.updateDocument("file:///test.grlx", src)
|
||||||
|
doc := h.getDocument("file:///test.grlx")
|
||||||
|
|
||||||
|
// Line 4 is inside pkg.installed properties, "name" is already used
|
||||||
|
items := h.completeProperties(doc, 4)
|
||||||
|
|
||||||
|
// Should offer "version" but NOT "name" (already used)
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Label == "- name: " {
|
||||||
|
t.Error("should not offer already-used property 'name'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should offer requisites
|
||||||
|
foundRequisites := false
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Label == "- requisites:" {
|
||||||
|
foundRequisites = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundRequisites {
|
||||||
|
t.Error("expected requisites in property completions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompletePropertiesNoStep(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:`
|
||||||
|
h.updateDocument("file:///test.grlx", src)
|
||||||
|
doc := h.getDocument("file:///test.grlx")
|
||||||
|
|
||||||
|
items := h.completeProperties(doc, 0)
|
||||||
|
if len(items) != 0 {
|
||||||
|
t.Errorf("expected no completions when no step found, got %d", len(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteRequisiteTypes(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
|
||||||
|
items := h.completeRequisiteTypes("")
|
||||||
|
if len(items) != len(schema.AllRequisiteTypes) {
|
||||||
|
t.Errorf("expected %d requisite types, got %d", len(schema.AllRequisiteTypes), len(items))
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Kind != protocol.CompletionItemKindEnum {
|
||||||
|
t.Errorf("expected Enum kind for requisite, got %v", item.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteStepIDs(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:
|
||||||
|
install nginx:
|
||||||
|
pkg.installed:
|
||||||
|
- name: nginx
|
||||||
|
start nginx:
|
||||||
|
service.running:
|
||||||
|
- name: nginx`
|
||||||
|
h.updateDocument("file:///test.grlx", src)
|
||||||
|
doc := h.getDocument("file:///test.grlx")
|
||||||
|
|
||||||
|
items := h.completeStepIDs(doc)
|
||||||
|
if len(items) != 2 {
|
||||||
|
t.Errorf("expected 2 step IDs, got %d", len(items))
|
||||||
|
}
|
||||||
|
labelSet := make(map[string]bool)
|
||||||
|
for _, item := range items {
|
||||||
|
labelSet[item.Label] = true
|
||||||
|
}
|
||||||
|
if !labelSet["install nginx"] || !labelSet["start nginx"] {
|
||||||
|
t.Errorf("missing expected step IDs, got: %v", labelSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteStepIDsNilRecipe(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
doc := &document{content: "", recipe: nil}
|
||||||
|
|
||||||
|
items := h.completeStepIDs(doc)
|
||||||
|
if len(items) != 0 {
|
||||||
|
t.Errorf("expected no step IDs for nil recipe, got %d", len(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsTopLevel(t *testing.T) {
|
||||||
|
content := "steps:\n install:\n pkg.installed:"
|
||||||
|
if !isTopLevel(content, 0) {
|
||||||
|
t.Error("line 0 should be top-level")
|
||||||
|
}
|
||||||
|
if isTopLevel(content, 1) {
|
||||||
|
t.Error("line 1 should not be top-level (indented)")
|
||||||
|
}
|
||||||
|
if isTopLevel(content, 99) {
|
||||||
|
t.Error("out-of-bounds line should not be top-level")
|
||||||
|
}
|
||||||
|
if isTopLevel(content, -1) {
|
||||||
|
t.Error("negative line should not be top-level")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsInRequisites(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
line string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{" - require: step1", true},
|
||||||
|
{" - onchanges: step1", true},
|
||||||
|
{" - onfail: step1", true},
|
||||||
|
{" - name: foo", false},
|
||||||
|
{"steps:", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := isInRequisites(tt.line)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("isInRequisites(%q) = %v, want %v", tt.line, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsInRequisiteValue(t *testing.T) {
|
||||||
|
content := `steps:
|
||||||
|
first:
|
||||||
|
file.exists:
|
||||||
|
- name: /tmp/a
|
||||||
|
- requisites:
|
||||||
|
- require:
|
||||||
|
- first`
|
||||||
|
|
||||||
|
if !isInRequisiteValue(content, 6) {
|
||||||
|
t.Error("line 6 should be in requisite value context")
|
||||||
|
}
|
||||||
|
if isInRequisiteValue(content, 0) {
|
||||||
|
t.Error("line 0 should not be in requisite value context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsPropertyPosition(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
line string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{" - name: foo", true},
|
||||||
|
{" - ", true},
|
||||||
|
{" ", true},
|
||||||
|
{"steps:", false},
|
||||||
|
{" - name: foo", false}, // only 2 spaces indent
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := isPropertyPosition(tt.line)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("isPropertyPosition(%q) = %v, want %v", tt.line, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMethodDoc(t *testing.T) {
|
||||||
|
ing := &schema.Ingredient{Name: "file", Description: "Manage files"}
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "managed",
|
||||||
|
Description: "Download and manage a file",
|
||||||
|
Properties: []schema.Property{
|
||||||
|
{Key: "name", Type: "string", Required: true, Description: "The file path"},
|
||||||
|
{Key: "source", Type: "string", Required: true, Description: "Source URL"},
|
||||||
|
{Key: "mode", Type: "string", Required: false},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := buildMethodDoc(ing, m)
|
||||||
|
if doc == "" {
|
||||||
|
t.Fatal("expected non-empty doc string")
|
||||||
|
}
|
||||||
|
// Required properties should be marked with *
|
||||||
|
if !contains(doc, "* name") {
|
||||||
|
t.Error("expected required marker for 'name'")
|
||||||
|
}
|
||||||
|
if !contains(doc, "* source") {
|
||||||
|
t.Error("expected required marker for 'source'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindStepForLine(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:
|
||||||
|
install nginx:
|
||||||
|
pkg.installed:
|
||||||
|
- name: nginx
|
||||||
|
start nginx:
|
||||||
|
service.running:
|
||||||
|
- name: nginx`
|
||||||
|
h.updateDocument("file:///test.grlx", src)
|
||||||
|
doc := h.getDocument("file:///test.grlx")
|
||||||
|
|
||||||
|
// Line 3 is inside "install nginx" step
|
||||||
|
step := h.findStepForLine(doc, 3)
|
||||||
|
if step == nil {
|
||||||
|
t.Fatal("expected to find step for line 3")
|
||||||
|
}
|
||||||
|
if step.Ingredient != "pkg" || step.Method != "installed" {
|
||||||
|
t.Errorf("expected pkg.installed, got %s.%s", step.Ingredient, step.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line 6 is inside "start nginx" step
|
||||||
|
step = h.findStepForLine(doc, 6)
|
||||||
|
if step == nil {
|
||||||
|
t.Fatal("expected to find step for line 6")
|
||||||
|
}
|
||||||
|
if step.Ingredient != "service" || step.Method != "running" {
|
||||||
|
t.Errorf("expected service.running, got %s.%s", step.Ingredient, step.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nil recipe
|
||||||
|
step = h.findStepForLine(&document{content: "", recipe: nil}, 0)
|
||||||
|
if step != nil {
|
||||||
|
t.Error("expected nil for nil recipe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsHelper(s, substr string) bool {
|
||||||
|
for i := 0; i+len(substr) <= len(s); i++ {
|
||||||
|
if s[i:i+len(substr)] == substr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
205
internal/lsp/diagnostics_test.go
Normal file
205
internal/lsp/diagnostics_test.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.lsp.dev/protocol"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/gogrlx/grlx-lsp/internal/recipe"
|
||||||
|
"github.com/gogrlx/grlx-lsp/internal/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiagnoseNilRecipe(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
doc := &document{content: "", recipe: nil}
|
||||||
|
diags := h.diagnose(doc)
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("expected no diagnostics for nil recipe, got %d", len(diags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiagnoseParseErrors(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:
|
||||||
|
bad step:
|
||||||
|
not.a.valid.format:
|
||||||
|
- name: foo`
|
||||||
|
doc := &document{
|
||||||
|
content: src,
|
||||||
|
recipe: recipe.Parse([]byte(src)),
|
||||||
|
}
|
||||||
|
diags := h.diagnose(doc)
|
||||||
|
// Should at least report the unknown ingredient
|
||||||
|
if len(diags) == 0 {
|
||||||
|
t.Error("expected diagnostics for invalid recipe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiagnoseEmptyIngredient(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
// Step with empty ingredient should be skipped
|
||||||
|
r := &recipe.Recipe{
|
||||||
|
Steps: []recipe.Step{
|
||||||
|
{ID: "test", Ingredient: "", Method: "run"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
doc := &document{content: "", recipe: r}
|
||||||
|
diags := h.diagnose(doc)
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("expected no diagnostics for empty ingredient, got %d", len(diags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiagnoseRequisiteUnknownRef(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:
|
||||||
|
first:
|
||||||
|
file.exists:
|
||||||
|
- name: /tmp/a
|
||||||
|
second:
|
||||||
|
file.exists:
|
||||||
|
- name: /tmp/b
|
||||||
|
- requisites:
|
||||||
|
- require: nonexistent_step`
|
||||||
|
doc := &document{
|
||||||
|
content: src,
|
||||||
|
recipe: recipe.Parse([]byte(src)),
|
||||||
|
}
|
||||||
|
diags := h.diagnose(doc)
|
||||||
|
found := false
|
||||||
|
for _, d := range diags {
|
||||||
|
if d.Severity == protocol.DiagnosticSeverityWarning && contains(d.Message, "reference to unknown step") {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("expected warning for reference to unknown step")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsValidRequisiteType(t *testing.T) {
|
||||||
|
validTypes := []string{"require", "require_any", "onchanges", "onchanges_any", "onfail", "onfail_any"}
|
||||||
|
for _, rt := range validTypes {
|
||||||
|
if !isValidRequisiteType(rt) {
|
||||||
|
t.Errorf("expected %q to be valid requisite type", rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isValidRequisiteType("bogus") {
|
||||||
|
t.Error("expected 'bogus' to be invalid requisite type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointRange(t *testing.T) {
|
||||||
|
r := pointRange(5, 10)
|
||||||
|
if r.Start.Line != 5 || r.Start.Character != 10 {
|
||||||
|
t.Errorf("unexpected start: %v", r.Start)
|
||||||
|
}
|
||||||
|
if r.End.Line != 5 || r.End.Character != 11 {
|
||||||
|
t.Errorf("unexpected end: %v", r.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative values should clamp to 0
|
||||||
|
r = pointRange(-1, -5)
|
||||||
|
if r.Start.Line != 0 || r.Start.Character != 0 {
|
||||||
|
t.Errorf("expected clamped to 0, got: %v", r.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYamlNodeRange(t *testing.T) {
|
||||||
|
// Nil node
|
||||||
|
r := yamlNodeRange(nil)
|
||||||
|
if r.Start.Line != 0 || r.Start.Character != 0 {
|
||||||
|
t.Errorf("expected (0,0) for nil node, got: %v", r.Start)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal node
|
||||||
|
node := &yaml.Node{Line: 3, Column: 5, Value: "hello"}
|
||||||
|
r = yamlNodeRange(node)
|
||||||
|
if r.Start.Line != 2 || r.Start.Character != 4 {
|
||||||
|
t.Errorf("expected (2,4), got: (%d,%d)", r.Start.Line, r.Start.Character)
|
||||||
|
}
|
||||||
|
if r.End.Character != 9 { // 4 + len("hello")
|
||||||
|
t.Errorf("expected end char 9, got: %d", r.End.Character)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishDiagnosticsNilConn(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
src := `steps:
|
||||||
|
test:
|
||||||
|
pkg.installed:
|
||||||
|
- name: nginx`
|
||||||
|
h.updateDocument("file:///test.grlx", src)
|
||||||
|
|
||||||
|
// Should not panic with nil conn
|
||||||
|
h.publishDiagnostics(t.Context(), "file:///test.grlx")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishDiagnosticsNoDoc(t *testing.T) {
|
||||||
|
h := NewHandler(schema.DefaultRegistry())
|
||||||
|
// Should not panic when document doesn't exist
|
||||||
|
h.publishDiagnostics(t.Context(), "file:///nonexistent.grlx")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckRequiredAllPresent(t *testing.T) {
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "managed",
|
||||||
|
Properties: []schema.Property{
|
||||||
|
{Key: "name", Type: "string", Required: true},
|
||||||
|
{Key: "source", Type: "string", Required: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := recipe.Step{
|
||||||
|
Properties: []recipe.PropertyEntry{
|
||||||
|
{Key: "name"},
|
||||||
|
{Key: "source"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
diags := checkRequired(s, m)
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("expected no diagnostics when all required present, got %d", len(diags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckUnknownNoUnknowns(t *testing.T) {
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "installed",
|
||||||
|
Properties: []schema.Property{
|
||||||
|
{Key: "name", Type: "string", Required: true},
|
||||||
|
{Key: "version", Type: "string"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := recipe.Step{
|
||||||
|
Ingredient: "pkg",
|
||||||
|
Method: "installed",
|
||||||
|
Properties: []recipe.PropertyEntry{
|
||||||
|
{Key: "name", KeyNode: &yaml.Node{Line: 1, Column: 1, Value: "name"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
diags := checkUnknown(s, m)
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("expected no unknown property diagnostics, got %d", len(diags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckUnknownRequisitesAllowed(t *testing.T) {
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "installed",
|
||||||
|
Properties: []schema.Property{
|
||||||
|
{Key: "name", Type: "string", Required: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := recipe.Step{
|
||||||
|
Ingredient: "pkg",
|
||||||
|
Method: "installed",
|
||||||
|
Properties: []recipe.PropertyEntry{
|
||||||
|
{Key: "name", KeyNode: &yaml.Node{Line: 1, Column: 1, Value: "name"}},
|
||||||
|
{Key: "requisites", KeyNode: &yaml.Node{Line: 2, Column: 1, Value: "requisites"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
diags := checkUnknown(s, m)
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("'requisites' should be allowed, got diagnostics: %v", diags)
|
||||||
|
}
|
||||||
|
}
|
||||||
128
internal/lsp/hover_test.go
Normal file
128
internal/lsp/hover_test.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gogrlx/grlx-lsp/internal/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildMethodMarkdown(t *testing.T) {
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "managed",
|
||||||
|
Description: "Download and manage a file from a source",
|
||||||
|
Properties: []schema.Property{
|
||||||
|
{Key: "name", Type: "string", Required: true, Description: "The file path"},
|
||||||
|
{Key: "source", Type: "string", Required: true, Description: "Source URL"},
|
||||||
|
{Key: "mode", Type: "string", Required: false, Description: "File permissions"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
md := buildMethodMarkdown("file", m)
|
||||||
|
|
||||||
|
// Should contain header
|
||||||
|
if !contains(md, "### file.managed") {
|
||||||
|
t.Error("expected markdown header with ingredient.method")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should contain description
|
||||||
|
if !contains(md, "Download and manage a file") {
|
||||||
|
t.Error("expected description in markdown")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should contain properties table
|
||||||
|
if !contains(md, "| Property |") {
|
||||||
|
t.Error("expected properties table")
|
||||||
|
}
|
||||||
|
if !contains(md, "| `name` |") {
|
||||||
|
t.Error("expected name property in table")
|
||||||
|
}
|
||||||
|
if !contains(md, "| `source` |") {
|
||||||
|
t.Error("expected source property in table")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required properties should show "yes"
|
||||||
|
if !contains(md, "| yes |") {
|
||||||
|
t.Error("expected 'yes' for required properties")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMethodMarkdownNoProperties(t *testing.T) {
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "cleaned",
|
||||||
|
Description: "Clean package cache",
|
||||||
|
}
|
||||||
|
|
||||||
|
md := buildMethodMarkdown("pkg", m)
|
||||||
|
if !contains(md, "### pkg.cleaned") {
|
||||||
|
t.Error("expected header")
|
||||||
|
}
|
||||||
|
// Should not contain a table
|
||||||
|
if contains(md, "| Property |") {
|
||||||
|
t.Error("should not have property table for method with no properties")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMethodMarkdownNoDescription(t *testing.T) {
|
||||||
|
m := &schema.Method{
|
||||||
|
Name: "test",
|
||||||
|
Properties: []schema.Property{
|
||||||
|
{Key: "name", Type: "string", Required: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
md := buildMethodMarkdown("foo", m)
|
||||||
|
if !contains(md, "### foo.test") {
|
||||||
|
t.Error("expected header")
|
||||||
|
}
|
||||||
|
if !contains(md, "| `name` |") {
|
||||||
|
t.Error("expected name property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsWordChar(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
b byte
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{'a', true},
|
||||||
|
{'z', true},
|
||||||
|
{'A', true},
|
||||||
|
{'Z', true},
|
||||||
|
{'0', true},
|
||||||
|
{'9', true},
|
||||||
|
{'_', true},
|
||||||
|
{'.', true},
|
||||||
|
{'-', true},
|
||||||
|
{' ', false},
|
||||||
|
{':', false},
|
||||||
|
{'\t', false},
|
||||||
|
{'(', false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := isWordChar(tt.b)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("isWordChar(%q) = %v, want %v", tt.b, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWordAtPositionEdgeCases(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
line string
|
||||||
|
col int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"", 0, ""},
|
||||||
|
{"hello", 100, "hello"}, // col beyond line length
|
||||||
|
{" file.managed:", 2, "file.managed"}, // col 2 is start of word
|
||||||
|
{"file.managed:", 12, "file.managed"}, // just before colon
|
||||||
|
{"a", 0, "a"},
|
||||||
|
{"a", 1, "a"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := wordAtPosition(tt.line, tt.col)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("wordAtPosition(%q, %d) = %q, want %q", tt.line, tt.col, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user