github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonformat/computed/renderers/renderer_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package renderers
     5  
     6  import (
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/terramate-io/tf/command/jsonformat/computed"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/mitchellh/colorstring"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/terramate-io/tf/plans"
    17  )
    18  
    19  func TestRenderers_Human(t *testing.T) {
    20  	colorize := colorstring.Colorize{
    21  		Colors:  colorstring.DefaultColors,
    22  		Disable: true,
    23  	}
    24  
    25  	tcs := map[string]struct {
    26  		diff     computed.Diff
    27  		expected string
    28  		opts     computed.RenderHumanOpts
    29  	}{
    30  		// We're using the string "null" in these tests to demonstrate the
    31  		// difference between rendering an actual string and rendering a null
    32  		// value.
    33  		"primitive_create_string": {
    34  			diff: computed.Diff{
    35  				Renderer: Primitive(nil, "null", cty.String),
    36  				Action:   plans.Create,
    37  			},
    38  			expected: "\"null\"",
    39  		},
    40  		"primitive_delete_string": {
    41  			diff: computed.Diff{
    42  				Renderer: Primitive("null", nil, cty.String),
    43  				Action:   plans.Delete,
    44  			},
    45  			expected: "\"null\" -> null",
    46  		},
    47  		"primitive_update_string_to_null": {
    48  			diff: computed.Diff{
    49  				Renderer: Primitive("null", nil, cty.String),
    50  				Action:   plans.Update,
    51  			},
    52  			expected: "\"null\" -> null",
    53  		},
    54  		"primitive_update_string_from_null": {
    55  			diff: computed.Diff{
    56  				Renderer: Primitive(nil, "null", cty.String),
    57  				Action:   plans.Update,
    58  			},
    59  			expected: "null -> \"null\"",
    60  		},
    61  		"primitive_update_multiline_string_to_null": {
    62  			diff: computed.Diff{
    63  				Renderer: Primitive("nu\nll", nil, cty.String),
    64  				Action:   plans.Update,
    65  			},
    66  			expected: `
    67  <<-EOT
    68        - nu
    69        - ll
    70        + null
    71      EOT
    72  `,
    73  		},
    74  		"primitive_update_multiline_string_from_null": {
    75  			diff: computed.Diff{
    76  				Renderer: Primitive(nil, "nu\nll", cty.String),
    77  				Action:   plans.Update,
    78  			},
    79  			expected: `
    80  <<-EOT
    81        - null
    82        + nu
    83        + ll
    84      EOT
    85  `,
    86  		},
    87  		"primitive_update_json_string_to_null": {
    88  			diff: computed.Diff{
    89  				Renderer: Primitive("[null]", nil, cty.String),
    90  				Action:   plans.Update,
    91  			},
    92  			expected: `
    93  jsonencode(
    94          [
    95            - null,
    96          ]
    97      ) -> null
    98  `,
    99  		},
   100  		"primitive_update_json_string_from_null": {
   101  			diff: computed.Diff{
   102  				Renderer: Primitive(nil, "[null]", cty.String),
   103  				Action:   plans.Update,
   104  			},
   105  			expected: `
   106  null -> jsonencode(
   107          [
   108            + null,
   109          ]
   110      )
   111  `,
   112  		},
   113  		"primitive_create_null_string": {
   114  			diff: computed.Diff{
   115  				Renderer: Primitive(nil, nil, cty.String),
   116  				Action:   plans.Create,
   117  			},
   118  			expected: "null",
   119  		},
   120  		"primitive_delete_null_string": {
   121  			diff: computed.Diff{
   122  				Renderer: Primitive(nil, nil, cty.String),
   123  				Action:   plans.Delete,
   124  			},
   125  			expected: "null",
   126  		},
   127  		"primitive_create": {
   128  			diff: computed.Diff{
   129  				Renderer: Primitive(nil, 1.0, cty.Number),
   130  				Action:   plans.Create,
   131  			},
   132  			expected: "1",
   133  		},
   134  		"primitive_delete": {
   135  			diff: computed.Diff{
   136  				Renderer: Primitive(1.0, nil, cty.Number),
   137  				Action:   plans.Delete,
   138  			},
   139  			expected: "1 -> null",
   140  		},
   141  		"primitive_delete_override": {
   142  			diff: computed.Diff{
   143  				Renderer: Primitive(1.0, nil, cty.Number),
   144  				Action:   plans.Delete,
   145  			},
   146  			opts:     computed.RenderHumanOpts{OverrideNullSuffix: true},
   147  			expected: "1",
   148  		},
   149  		"primitive_update_to_null": {
   150  			diff: computed.Diff{
   151  				Renderer: Primitive(1.0, nil, cty.Number),
   152  				Action:   plans.Update,
   153  			},
   154  			expected: "1 -> null",
   155  		},
   156  		"primitive_update_from_null": {
   157  			diff: computed.Diff{
   158  				Renderer: Primitive(nil, 1.0, cty.Number),
   159  				Action:   plans.Update,
   160  			},
   161  			expected: "null -> 1",
   162  		},
   163  		"primitive_update": {
   164  			diff: computed.Diff{
   165  				Renderer: Primitive(0.0, 1.0, cty.Number),
   166  				Action:   plans.Update,
   167  			},
   168  			expected: "0 -> 1",
   169  		},
   170  		"primitive_update_long_float": {
   171  			diff: computed.Diff{
   172  				Renderer: Primitive(123456789.0, 987654321.0, cty.Number),
   173  				Action:   plans.Update,
   174  			},
   175  			expected: "123456789 -> 987654321",
   176  		},
   177  		"primitive_update_replace": {
   178  			diff: computed.Diff{
   179  				Renderer: Primitive(0.0, 1.0, cty.Number),
   180  				Action:   plans.Update,
   181  				Replace:  true,
   182  			},
   183  			expected: "0 -> 1 # forces replacement",
   184  		},
   185  		"primitive_multiline_string_create": {
   186  			diff: computed.Diff{
   187  				Renderer: Primitive(nil, "hello\nworld", cty.String),
   188  				Action:   plans.Create,
   189  			},
   190  			expected: `
   191  <<-EOT
   192          hello
   193          world
   194      EOT
   195  `,
   196  		},
   197  		"primitive_multiline_string_delete": {
   198  			diff: computed.Diff{
   199  				Renderer: Primitive("hello\nworld", nil, cty.String),
   200  				Action:   plans.Delete,
   201  			},
   202  			expected: `
   203  <<-EOT
   204          hello
   205          world
   206      EOT -> null
   207  `,
   208  		},
   209  		"primitive_multiline_string_update": {
   210  			diff: computed.Diff{
   211  				Renderer: Primitive("hello\nold\nworld", "hello\nnew\nworld", cty.String),
   212  				Action:   plans.Update,
   213  			},
   214  			expected: `
   215  <<-EOT
   216          hello
   217        - old
   218        + new
   219          world
   220      EOT
   221  `,
   222  		},
   223  		"primitive_json_string_create": {
   224  			diff: computed.Diff{
   225  				Renderer: Primitive(nil, "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   226  				Action:   plans.Create,
   227  			},
   228  			expected: `
   229  jsonencode(
   230          {
   231            + key_one = "value_one"
   232            + key_two = "value_two"
   233          }
   234      )
   235  `,
   236  		},
   237  		"primitive_json_string_delete": {
   238  			diff: computed.Diff{
   239  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", nil, cty.String),
   240  				Action:   plans.Delete,
   241  			},
   242  			expected: `
   243  jsonencode(
   244          {
   245            - key_one = "value_one"
   246            - key_two = "value_two"
   247          }
   248      ) -> null
   249  `,
   250  		},
   251  		"primitive_json_string_update": {
   252  			diff: computed.Diff{
   253  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\",\"key_three\":\"value_three\"}", cty.String),
   254  				Action:   plans.Update,
   255  			},
   256  			expected: `
   257  jsonencode(
   258        ~ {
   259            + key_three = "value_three"
   260              # (2 unchanged attributes hidden)
   261          }
   262      )
   263  `,
   264  		},
   265  		"primitive_json_explicit_nulls": {
   266  			diff: computed.Diff{
   267  				Renderer: Primitive("{\"key_one\":\"value_one\",\"key_two\":\"value_two\"}", "{\"key_one\":null}", cty.String),
   268  				Action:   plans.Update,
   269  			},
   270  			expected: `
   271  jsonencode(
   272        ~ {
   273            ~ key_one = "value_one" -> null
   274            - key_two = "value_two"
   275          }
   276      )
   277  `,
   278  		},
   279  		"primitive_fake_json_string_update": {
   280  			diff: computed.Diff{
   281  				// This isn't valid JSON, our renderer should be okay with it.
   282  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\",\"key_three\":\"value_three\"", cty.String),
   283  				Action:   plans.Update,
   284  			},
   285  			expected: "\"{\\\"key_one\\\": \\\"value_one\\\",\\\"key_two\\\":\\\"value_two\\\"\" -> \"{\\\"key_one\\\": \\\"value_one\\\",\\\"key_two\\\":\\\"value_two\\\",\\\"key_three\\\":\\\"value_three\\\"\"",
   286  		},
   287  		"primitive_multiline_to_json_update": {
   288  			diff: computed.Diff{
   289  				Renderer: Primitive("hello\nworld", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   290  				Action:   plans.Update,
   291  			},
   292  			expected: `
   293  <<-EOT
   294          hello
   295          world
   296      EOT -> jsonencode(
   297          {
   298            + key_one = "value_one"
   299            + key_two = "value_two"
   300          }
   301      )
   302  `,
   303  		},
   304  		"primitive_json_to_multiline_update": {
   305  			diff: computed.Diff{
   306  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "hello\nworld", cty.String),
   307  				Action:   plans.Update,
   308  			},
   309  			expected: `
   310  jsonencode(
   311          {
   312            - key_one = "value_one"
   313            - key_two = "value_two"
   314          }
   315      ) -> <<-EOT
   316          hello
   317          world
   318      EOT
   319  `,
   320  		},
   321  		"primitive_json_to_string_update": {
   322  			diff: computed.Diff{
   323  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "hello world", cty.String),
   324  				Action:   plans.Update,
   325  			},
   326  			expected: `
   327  jsonencode(
   328          {
   329            - key_one = "value_one"
   330            - key_two = "value_two"
   331          }
   332      ) -> "hello world"
   333  `,
   334  		},
   335  		"primitive_string_to_json_update": {
   336  			diff: computed.Diff{
   337  				Renderer: Primitive("hello world", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   338  				Action:   plans.Update,
   339  			},
   340  			expected: `
   341  "hello world" -> jsonencode(
   342          {
   343            + key_one = "value_one"
   344            + key_two = "value_two"
   345          }
   346      )
   347  `,
   348  		},
   349  		"primitive_multi_to_single_update": {
   350  			diff: computed.Diff{
   351  				Renderer: Primitive("hello\nworld", "hello world", cty.String),
   352  				Action:   plans.Update,
   353  			},
   354  			expected: `
   355  <<-EOT
   356        - hello
   357        - world
   358        + hello world
   359      EOT
   360  `,
   361  		},
   362  		"primitive_single_to_multi_update": {
   363  			diff: computed.Diff{
   364  				Renderer: Primitive("hello world", "hello\nworld", cty.String),
   365  				Action:   plans.Update,
   366  			},
   367  			expected: `
   368  <<-EOT
   369        - hello world
   370        + hello
   371        + world
   372      EOT
   373  `,
   374  		},
   375  		"sensitive_update": {
   376  			diff: computed.Diff{
   377  				Renderer: Sensitive(computed.Diff{
   378  					Renderer: Primitive(0.0, 1.0, cty.Number),
   379  					Action:   plans.Update,
   380  				}, true, true),
   381  				Action: plans.Update,
   382  			},
   383  			expected: "(sensitive value)",
   384  		},
   385  		"sensitive_update_replace": {
   386  			diff: computed.Diff{
   387  				Renderer: Sensitive(computed.Diff{
   388  					Renderer: Primitive(0.0, 1.0, cty.Number),
   389  					Action:   plans.Update,
   390  					Replace:  true,
   391  				}, true, true),
   392  				Action:  plans.Update,
   393  				Replace: true,
   394  			},
   395  			expected: "(sensitive value) # forces replacement",
   396  		},
   397  		"computed_create": {
   398  			diff: computed.Diff{
   399  				Renderer: Unknown(computed.Diff{}),
   400  				Action:   plans.Create,
   401  			},
   402  			expected: "(known after apply)",
   403  		},
   404  		"computed_update": {
   405  			diff: computed.Diff{
   406  				Renderer: Unknown(computed.Diff{
   407  					Renderer: Primitive(0.0, nil, cty.Number),
   408  					Action:   plans.Delete,
   409  				}),
   410  				Action: plans.Update,
   411  			},
   412  			expected: "0 -> (known after apply)",
   413  		},
   414  		"computed_create_forces_replacement": {
   415  			diff: computed.Diff{
   416  				Renderer: Unknown(computed.Diff{}),
   417  				Action:   plans.Create,
   418  				Replace:  true,
   419  			},
   420  			expected: "(known after apply) # forces replacement",
   421  		},
   422  		"computed_update_forces_replacement": {
   423  			diff: computed.Diff{
   424  				Renderer: Unknown(computed.Diff{
   425  					Renderer: Primitive(0.0, nil, cty.Number),
   426  					Action:   plans.Delete,
   427  				}),
   428  				Action:  plans.Update,
   429  				Replace: true,
   430  			},
   431  			expected: "0 -> (known after apply) # forces replacement",
   432  		},
   433  		"object_created": {
   434  			diff: computed.Diff{
   435  				Renderer: Object(map[string]computed.Diff{}),
   436  				Action:   plans.Create,
   437  			},
   438  			expected: "{}",
   439  		},
   440  		"object_created_with_attributes": {
   441  			diff: computed.Diff{
   442  				Renderer: Object(map[string]computed.Diff{
   443  					"attribute_one": {
   444  						Renderer: Primitive(nil, 0.0, cty.Number),
   445  						Action:   plans.Create,
   446  					},
   447  				}),
   448  				Action: plans.Create,
   449  			},
   450  			expected: `
   451  {
   452        + attribute_one = 0
   453      }
   454  `,
   455  		},
   456  		"object_deleted": {
   457  			diff: computed.Diff{
   458  				Renderer: Object(map[string]computed.Diff{}),
   459  				Action:   plans.Delete,
   460  			},
   461  			expected: "{} -> null",
   462  		},
   463  		"object_deleted_with_attributes": {
   464  			diff: computed.Diff{
   465  				Renderer: Object(map[string]computed.Diff{
   466  					"attribute_one": {
   467  						Renderer: Primitive(0.0, nil, cty.Number),
   468  						Action:   plans.Delete,
   469  					},
   470  				}),
   471  				Action: plans.Delete,
   472  			},
   473  			expected: `
   474  {
   475        - attribute_one = 0
   476      } -> null
   477  `,
   478  		},
   479  		"nested_object_deleted": {
   480  			diff: computed.Diff{
   481  				Renderer: NestedObject(map[string]computed.Diff{}),
   482  				Action:   plans.Delete,
   483  			},
   484  			expected: "{} -> null",
   485  		},
   486  		"nested_object_deleted_with_attributes": {
   487  			diff: computed.Diff{
   488  				Renderer: NestedObject(map[string]computed.Diff{
   489  					"attribute_one": {
   490  						Renderer: Primitive(0.0, nil, cty.Number),
   491  						Action:   plans.Delete,
   492  					},
   493  				}),
   494  				Action: plans.Delete,
   495  			},
   496  			expected: `
   497  {
   498        - attribute_one = 0 -> null
   499      } -> null
   500  `,
   501  		},
   502  		"object_create_attribute": {
   503  			diff: computed.Diff{
   504  				Renderer: Object(map[string]computed.Diff{
   505  					"attribute_one": {
   506  						Renderer: Primitive(nil, 0.0, cty.Number),
   507  						Action:   plans.Create,
   508  					},
   509  				}),
   510  				Action: plans.Update,
   511  			},
   512  			expected: `
   513  {
   514        + attribute_one = 0
   515      }
   516  `,
   517  		},
   518  		"object_update_attribute": {
   519  			diff: computed.Diff{
   520  				Renderer: Object(map[string]computed.Diff{
   521  					"attribute_one": {
   522  						Renderer: Primitive(0.0, 1.0, cty.Number),
   523  						Action:   plans.Update,
   524  					},
   525  				}),
   526  				Action: plans.Update,
   527  			},
   528  			expected: `
   529  {
   530        ~ attribute_one = 0 -> 1
   531      }
   532  `,
   533  		},
   534  		"object_update_attribute_forces_replacement": {
   535  			diff: computed.Diff{
   536  				Renderer: Object(map[string]computed.Diff{
   537  					"attribute_one": {
   538  						Renderer: Primitive(0.0, 1.0, cty.Number),
   539  						Action:   plans.Update,
   540  					},
   541  				}),
   542  				Action:  plans.Update,
   543  				Replace: true,
   544  			},
   545  			expected: `
   546  { # forces replacement
   547        ~ attribute_one = 0 -> 1
   548      }
   549  `,
   550  		},
   551  		"object_delete_attribute": {
   552  			diff: computed.Diff{
   553  				Renderer: Object(map[string]computed.Diff{
   554  					"attribute_one": {
   555  						Renderer: Primitive(0.0, nil, cty.Number),
   556  						Action:   plans.Delete,
   557  					},
   558  				}),
   559  				Action: plans.Update,
   560  			},
   561  			expected: `
   562  {
   563        - attribute_one = 0
   564      }
   565  `,
   566  		},
   567  		"object_ignore_unchanged_attributes": {
   568  			diff: computed.Diff{
   569  				Renderer: Object(map[string]computed.Diff{
   570  					"attribute_one": {
   571  						Renderer: Primitive(0.0, 1.0, cty.Number),
   572  						Action:   plans.Update,
   573  					},
   574  					"attribute_two": {
   575  						Renderer: Primitive(0.0, 0.0, cty.Number),
   576  						Action:   plans.NoOp,
   577  					},
   578  					"attribute_three": {
   579  						Renderer: Primitive(nil, 1.0, cty.Number),
   580  						Action:   plans.Create,
   581  					},
   582  				}),
   583  				Action: plans.Update,
   584  			},
   585  			expected: `
   586  {
   587        ~ attribute_one   = 0 -> 1
   588        + attribute_three = 1
   589          # (1 unchanged attribute hidden)
   590      }
   591  `,
   592  		},
   593  		"object_create_sensitive_attribute": {
   594  			diff: computed.Diff{
   595  				Renderer: Object(map[string]computed.Diff{
   596  					"attribute_one": {
   597  						Renderer: Sensitive(computed.Diff{
   598  							Renderer: Primitive(nil, 1.0, cty.Number),
   599  							Action:   plans.Create,
   600  						}, false, true),
   601  						Action: plans.Create,
   602  					},
   603  				}),
   604  				Action: plans.Update,
   605  			},
   606  			expected: `
   607  {
   608        + attribute_one = (sensitive value)
   609      }
   610  `,
   611  		},
   612  		"object_update_sensitive_attribute": {
   613  			diff: computed.Diff{
   614  				Renderer: Object(map[string]computed.Diff{
   615  					"attribute_one": {
   616  						Renderer: Sensitive(computed.Diff{
   617  							Renderer: Primitive(0.0, 1.0, cty.Number),
   618  							Action:   plans.Update,
   619  						}, true, true),
   620  						Action: plans.Update,
   621  					},
   622  				}),
   623  				Action: plans.Update,
   624  			},
   625  			expected: `
   626  {
   627        ~ attribute_one = (sensitive value)
   628      }
   629  `,
   630  		},
   631  		"object_delete_sensitive_attribute": {
   632  			diff: computed.Diff{
   633  				Renderer: Object(map[string]computed.Diff{
   634  					"attribute_one": {
   635  						Renderer: Sensitive(computed.Diff{
   636  							Renderer: Primitive(0.0, nil, cty.Number),
   637  							Action:   plans.Delete,
   638  						}, true, false),
   639  						Action: plans.Delete,
   640  					},
   641  				}),
   642  				Action: plans.Update,
   643  			},
   644  			expected: `
   645  {
   646        - attribute_one = (sensitive value)
   647      }
   648  `,
   649  		},
   650  		"object_create_computed_attribute": {
   651  			diff: computed.Diff{
   652  				Renderer: Object(map[string]computed.Diff{
   653  					"attribute_one": {
   654  						Renderer: Unknown(computed.Diff{Renderer: nil}),
   655  						Action:   plans.Create,
   656  					},
   657  				}),
   658  				Action: plans.Update,
   659  			},
   660  			expected: `
   661  {
   662        + attribute_one = (known after apply)
   663      }
   664  `,
   665  		},
   666  		"object_update_computed_attribute": {
   667  			diff: computed.Diff{
   668  				Renderer: Object(map[string]computed.Diff{
   669  					"attribute_one": {
   670  						Renderer: Unknown(computed.Diff{
   671  							Renderer: Primitive(1.0, nil, cty.Number),
   672  							Action:   plans.Delete,
   673  						}),
   674  						Action: plans.Update,
   675  					},
   676  				}),
   677  				Action: plans.Update,
   678  			},
   679  			expected: `
   680  {
   681        ~ attribute_one = 1 -> (known after apply)
   682      }
   683  `,
   684  		},
   685  		"object_escapes_attribute_keys": {
   686  			diff: computed.Diff{
   687  				Renderer: Object(map[string]computed.Diff{
   688  					"attribute_one": {
   689  						Renderer: Primitive(1.0, 2.0, cty.Number),
   690  						Action:   plans.Update,
   691  					},
   692  					"attribute:two": {
   693  						Renderer: Primitive(2.0, 3.0, cty.Number),
   694  						Action:   plans.Update,
   695  					},
   696  					"attribute_six": {
   697  						Renderer: Primitive(3.0, 4.0, cty.Number),
   698  						Action:   plans.Update,
   699  					},
   700  				}),
   701  				Action: plans.Update,
   702  			},
   703  			expected: `
   704  {
   705        ~ "attribute:two" = 2 -> 3
   706        ~ attribute_one   = 1 -> 2
   707        ~ attribute_six   = 3 -> 4
   708      }
   709  `,
   710  		},
   711  		"map_create_empty": {
   712  			diff: computed.Diff{
   713  				Renderer: Map(map[string]computed.Diff{}),
   714  				Action:   plans.Create,
   715  			},
   716  			expected: "{}",
   717  		},
   718  		"map_create": {
   719  			diff: computed.Diff{
   720  				Renderer: Map(map[string]computed.Diff{
   721  					"element_one": {
   722  						Renderer: Primitive(nil, "new", cty.String),
   723  						Action:   plans.Create,
   724  					},
   725  				}),
   726  				Action: plans.Create,
   727  			},
   728  			expected: `
   729  {
   730        + "element_one" = "new"
   731      }
   732  `,
   733  		},
   734  		"map_delete_empty": {
   735  			diff: computed.Diff{
   736  				Renderer: Map(map[string]computed.Diff{}),
   737  				Action:   plans.Delete,
   738  			},
   739  			expected: "{} -> null",
   740  		},
   741  		"map_delete": {
   742  			diff: computed.Diff{
   743  				Renderer: Map(map[string]computed.Diff{
   744  					"element_one": {
   745  						Renderer: Primitive("old", nil, cty.String),
   746  						Action:   plans.Delete,
   747  					},
   748  				}),
   749  				Action: plans.Delete,
   750  			},
   751  			expected: `
   752  {
   753        - "element_one" = "old"
   754      } -> null
   755  `,
   756  		},
   757  		"map_create_element": {
   758  			diff: computed.Diff{
   759  				Renderer: Map(map[string]computed.Diff{
   760  					"element_one": {
   761  						Renderer: Primitive(nil, "new", cty.String),
   762  						Action:   plans.Create,
   763  					},
   764  				}),
   765  				Action: plans.Update,
   766  			},
   767  			expected: `
   768  {
   769        + "element_one" = "new"
   770      }
   771  `,
   772  		},
   773  		"map_update_element": {
   774  			diff: computed.Diff{
   775  				Renderer: Map(map[string]computed.Diff{
   776  					"element_one": {
   777  						Renderer: Primitive("old", "new", cty.String),
   778  						Action:   plans.Update,
   779  					},
   780  				}),
   781  				Action: plans.Update,
   782  			},
   783  			expected: `
   784  {
   785        ~ "element_one" = "old" -> "new"
   786      }
   787  `,
   788  		},
   789  		"map_delete_element": {
   790  			diff: computed.Diff{
   791  				Renderer: Map(map[string]computed.Diff{
   792  					"element_one": {
   793  						Renderer: Primitive("old", nil, cty.String),
   794  						Action:   plans.Delete,
   795  					},
   796  				}),
   797  				Action: plans.Update,
   798  			},
   799  			expected: `
   800  {
   801        - "element_one" = "old" -> null
   802      }
   803  `,
   804  		},
   805  		"map_update_forces_replacement": {
   806  			diff: computed.Diff{
   807  				Renderer: Map(map[string]computed.Diff{
   808  					"element_one": {
   809  						Renderer: Primitive("old", "new", cty.String),
   810  						Action:   plans.Update,
   811  					},
   812  				}),
   813  				Action:  plans.Update,
   814  				Replace: true,
   815  			},
   816  			expected: `
   817  { # forces replacement
   818        ~ "element_one" = "old" -> "new"
   819      }
   820  `,
   821  		},
   822  		"map_ignore_unchanged_elements": {
   823  			diff: computed.Diff{
   824  				Renderer: Map(map[string]computed.Diff{
   825  					"element_one": {
   826  						Renderer: Primitive(nil, "new", cty.String),
   827  						Action:   plans.Create,
   828  					},
   829  					"element_two": {
   830  						Renderer: Primitive("old", "old", cty.String),
   831  						Action:   plans.NoOp,
   832  					},
   833  					"element_three": {
   834  						Renderer: Primitive("old", "new", cty.String),
   835  						Action:   plans.Update,
   836  					},
   837  				}),
   838  				Action: plans.Update,
   839  			},
   840  			expected: `
   841  {
   842        + "element_one"   = "new"
   843        ~ "element_three" = "old" -> "new"
   844          # (1 unchanged element hidden)
   845      }
   846  `,
   847  		},
   848  		"map_create_sensitive_element": {
   849  			diff: computed.Diff{
   850  				Renderer: Map(map[string]computed.Diff{
   851  					"element_one": {
   852  						Renderer: Sensitive(computed.Diff{
   853  							Renderer: Primitive(nil, 1.0, cty.Number),
   854  							Action:   plans.Create,
   855  						}, false, true),
   856  						Action: plans.Create,
   857  					},
   858  				}),
   859  				Action: plans.Update,
   860  			},
   861  			expected: `
   862  {
   863        + "element_one" = (sensitive value)
   864      }
   865  `,
   866  		},
   867  		"map_update_sensitive_element": {
   868  			diff: computed.Diff{
   869  				Renderer: Map(map[string]computed.Diff{
   870  					"element_one": {
   871  						Renderer: Sensitive(computed.Diff{
   872  							Renderer: Primitive(0.0, 1.0, cty.Number),
   873  							Action:   plans.Update,
   874  						}, true, true),
   875  						Action: plans.Update,
   876  					},
   877  				}),
   878  				Action: plans.Update,
   879  			},
   880  			expected: `
   881  {
   882        ~ "element_one" = (sensitive value)
   883      }
   884  `,
   885  		},
   886  		"map_update_sensitive_element_status": {
   887  			diff: computed.Diff{
   888  				Renderer: Map(map[string]computed.Diff{
   889  					"element_one": {
   890  						Renderer: Sensitive(computed.Diff{
   891  							Renderer: Primitive(0.0, 0.0, cty.Number),
   892  							Action:   plans.NoOp,
   893  						}, true, false),
   894  						Action: plans.Update,
   895  					},
   896  				}),
   897  				Action: plans.Update,
   898  			},
   899  			expected: `
   900  {
   901        # Warning: this attribute value will no longer be marked as sensitive
   902        # after applying this change. The value is unchanged.
   903        ~ "element_one" = (sensitive value)
   904      }
   905  `,
   906  		},
   907  		"map_delete_sensitive_element": {
   908  			diff: computed.Diff{
   909  				Renderer: Map(map[string]computed.Diff{
   910  					"element_one": {
   911  						Renderer: Sensitive(computed.Diff{
   912  							Renderer: Primitive(0.0, nil, cty.Number),
   913  							Action:   plans.Delete,
   914  						}, true, false),
   915  						Action: plans.Delete,
   916  					},
   917  				}),
   918  				Action: plans.Update,
   919  			},
   920  			expected: `
   921  {
   922        - "element_one" = (sensitive value) -> null
   923      }
   924  `,
   925  		},
   926  		"map_create_computed_element": {
   927  			diff: computed.Diff{
   928  				Renderer: Map(map[string]computed.Diff{
   929  					"element_one": {
   930  						Renderer: Unknown(computed.Diff{}),
   931  						Action:   plans.Create,
   932  					},
   933  				}),
   934  				Action: plans.Update,
   935  			},
   936  			expected: `
   937  {
   938        + "element_one" = (known after apply)
   939      }
   940  `,
   941  		},
   942  		"map_update_computed_element": {
   943  			diff: computed.Diff{
   944  				Renderer: Map(map[string]computed.Diff{
   945  					"element_one": {
   946  						Renderer: Unknown(computed.Diff{
   947  							Renderer: Primitive(1.0, nil, cty.Number),
   948  							Action:   plans.Delete,
   949  						}),
   950  						Action: plans.Update,
   951  					},
   952  				}),
   953  				Action: plans.Update,
   954  			},
   955  			expected: `
   956  {
   957        ~ "element_one" = 1 -> (known after apply)
   958      }
   959  `,
   960  		},
   961  		"map_aligns_key": {
   962  			diff: computed.Diff{
   963  				Renderer: Map(map[string]computed.Diff{
   964  					"element_one": {
   965  						Renderer: Primitive(1.0, 2.0, cty.Number),
   966  						Action:   plans.Update,
   967  					},
   968  					"element_two": {
   969  						Renderer: Primitive(1.0, 3.0, cty.Number),
   970  						Action:   plans.Update,
   971  					},
   972  					"element_three": {
   973  						Renderer: Primitive(1.0, 4.0, cty.Number),
   974  						Action:   plans.Update,
   975  					},
   976  				}),
   977  				Action: plans.Update,
   978  			},
   979  			expected: `
   980  {
   981        ~ "element_one"   = 1 -> 2
   982        ~ "element_three" = 1 -> 4
   983        ~ "element_two"   = 1 -> 3
   984      }
   985  `,
   986  		},
   987  		"nested_map_does_not_align_keys": {
   988  			diff: computed.Diff{
   989  				Renderer: NestedMap(map[string]computed.Diff{
   990  					"element_one": {
   991  						Renderer: Primitive(1.0, 2.0, cty.Number),
   992  						Action:   plans.Update,
   993  					},
   994  					"element_two": {
   995  						Renderer: Primitive(1.0, 3.0, cty.Number),
   996  						Action:   plans.Update,
   997  					},
   998  					"element_three": {
   999  						Renderer: Primitive(1.0, 4.0, cty.Number),
  1000  						Action:   plans.Update,
  1001  					},
  1002  				}),
  1003  				Action: plans.Update,
  1004  			},
  1005  			expected: `
  1006  {
  1007        ~ "element_one" = 1 -> 2
  1008        ~ "element_three" = 1 -> 4
  1009        ~ "element_two" = 1 -> 3
  1010      }
  1011  `,
  1012  		},
  1013  		"list_create_empty": {
  1014  			diff: computed.Diff{
  1015  				Renderer: List([]computed.Diff{}),
  1016  				Action:   plans.Create,
  1017  			},
  1018  			expected: "[]",
  1019  		},
  1020  		"list_create": {
  1021  			diff: computed.Diff{
  1022  				Renderer: List([]computed.Diff{
  1023  					{
  1024  						Renderer: Primitive(nil, 1.0, cty.Number),
  1025  						Action:   plans.Create,
  1026  					},
  1027  				}),
  1028  				Action: plans.Create,
  1029  			},
  1030  			expected: `
  1031  [
  1032        + 1,
  1033      ]
  1034  `,
  1035  		},
  1036  		"list_delete_empty": {
  1037  			diff: computed.Diff{
  1038  				Renderer: List([]computed.Diff{}),
  1039  				Action:   plans.Delete,
  1040  			},
  1041  			expected: "[] -> null",
  1042  		},
  1043  		"list_delete": {
  1044  			diff: computed.Diff{
  1045  				Renderer: List([]computed.Diff{
  1046  					{
  1047  						Renderer: Primitive(1.0, nil, cty.Number),
  1048  						Action:   plans.Delete,
  1049  					},
  1050  				}),
  1051  				Action: plans.Delete,
  1052  			},
  1053  			expected: `
  1054  [
  1055        - 1,
  1056      ] -> null
  1057  `,
  1058  		},
  1059  		"list_create_element": {
  1060  			diff: computed.Diff{
  1061  				Renderer: List([]computed.Diff{
  1062  					{
  1063  						Renderer: Primitive(nil, 1.0, cty.Number),
  1064  						Action:   plans.Create,
  1065  					},
  1066  				}),
  1067  				Action: plans.Update,
  1068  			},
  1069  			expected: `
  1070  [
  1071        + 1,
  1072      ]
  1073  `,
  1074  		},
  1075  		"list_update_element": {
  1076  			diff: computed.Diff{
  1077  				Renderer: List([]computed.Diff{
  1078  					{
  1079  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1080  						Action:   plans.Update,
  1081  					},
  1082  				}),
  1083  				Action: plans.Update,
  1084  			},
  1085  			expected: `
  1086  [
  1087        ~ 0 -> 1,
  1088      ]
  1089  `,
  1090  		},
  1091  		"list_replace_element": {
  1092  			diff: computed.Diff{
  1093  				Renderer: List([]computed.Diff{
  1094  					{
  1095  						Renderer: Primitive(0.0, nil, cty.Number),
  1096  						Action:   plans.Delete,
  1097  					},
  1098  					{
  1099  						Renderer: Primitive(nil, 1.0, cty.Number),
  1100  						Action:   plans.Create,
  1101  					},
  1102  				}),
  1103  				Action: plans.Update,
  1104  			},
  1105  			expected: `
  1106  [
  1107        - 0,
  1108        + 1,
  1109      ]
  1110  `,
  1111  		},
  1112  		"list_delete_element": {
  1113  			diff: computed.Diff{
  1114  				Renderer: List([]computed.Diff{
  1115  					{
  1116  						Renderer: Primitive(0.0, nil, cty.Number),
  1117  						Action:   plans.Delete,
  1118  					},
  1119  				}),
  1120  				Action: plans.Update,
  1121  			},
  1122  			expected: `
  1123  [
  1124        - 0,
  1125      ]
  1126  `,
  1127  		},
  1128  		"list_update_forces_replacement": {
  1129  			diff: computed.Diff{
  1130  				Renderer: List([]computed.Diff{
  1131  					{
  1132  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1133  						Action:   plans.Update,
  1134  					},
  1135  				}),
  1136  				Action:  plans.Update,
  1137  				Replace: true,
  1138  			},
  1139  			expected: `
  1140  [ # forces replacement
  1141        ~ 0 -> 1,
  1142      ]
  1143  `,
  1144  		},
  1145  		"list_update_ignores_unchanged": {
  1146  			diff: computed.Diff{
  1147  				Renderer: NestedList([]computed.Diff{
  1148  					{
  1149  						Renderer: Primitive(0.0, 0.0, cty.Number),
  1150  						Action:   plans.NoOp,
  1151  					},
  1152  					{
  1153  						Renderer: Primitive(1.0, 1.0, cty.Number),
  1154  						Action:   plans.NoOp,
  1155  					},
  1156  					{
  1157  						Renderer: Primitive(2.0, 5.0, cty.Number),
  1158  						Action:   plans.Update,
  1159  					},
  1160  					{
  1161  						Renderer: Primitive(3.0, 3.0, cty.Number),
  1162  						Action:   plans.NoOp,
  1163  					},
  1164  					{
  1165  						Renderer: Primitive(4.0, 4.0, cty.Number),
  1166  						Action:   plans.NoOp,
  1167  					},
  1168  				}),
  1169  				Action: plans.Update,
  1170  			},
  1171  			expected: `
  1172  [
  1173        ~ 2 -> 5,
  1174          # (4 unchanged elements hidden)
  1175      ]
  1176  `,
  1177  		},
  1178  		"list_update_ignored_unchanged_with_context": {
  1179  			diff: computed.Diff{
  1180  				Renderer: List([]computed.Diff{
  1181  					{
  1182  						Renderer: Primitive(0.0, 0.0, cty.Number),
  1183  						Action:   plans.NoOp,
  1184  					},
  1185  					{
  1186  						Renderer: Primitive(1.0, 1.0, cty.Number),
  1187  						Action:   plans.NoOp,
  1188  					},
  1189  					{
  1190  						Renderer: Primitive(2.0, 5.0, cty.Number),
  1191  						Action:   plans.Update,
  1192  					},
  1193  					{
  1194  						Renderer: Primitive(3.0, 3.0, cty.Number),
  1195  						Action:   plans.NoOp,
  1196  					},
  1197  					{
  1198  						Renderer: Primitive(4.0, 4.0, cty.Number),
  1199  						Action:   plans.NoOp,
  1200  					},
  1201  				}),
  1202  				Action: plans.Update,
  1203  			},
  1204  			expected: `
  1205  [
  1206          # (1 unchanged element hidden)
  1207          1,
  1208        ~ 2 -> 5,
  1209          3,
  1210          # (1 unchanged element hidden)
  1211      ]
  1212  `,
  1213  		},
  1214  		"list_create_sensitive_element": {
  1215  			diff: computed.Diff{
  1216  				Renderer: List([]computed.Diff{
  1217  					{
  1218  						Renderer: Sensitive(computed.Diff{
  1219  							Renderer: Primitive(nil, 1.0, cty.Number),
  1220  							Action:   plans.Create,
  1221  						}, false, true),
  1222  						Action: plans.Create,
  1223  					},
  1224  				}),
  1225  				Action: plans.Update,
  1226  			},
  1227  			expected: `
  1228  [
  1229        + (sensitive value),
  1230      ]
  1231  `,
  1232  		},
  1233  		"list_delete_sensitive_element": {
  1234  			diff: computed.Diff{
  1235  				Renderer: List([]computed.Diff{
  1236  					{
  1237  						Renderer: Sensitive(computed.Diff{
  1238  							Renderer: Primitive(1.0, nil, cty.Number),
  1239  							Action:   plans.Delete,
  1240  						}, true, false),
  1241  						Action: plans.Delete,
  1242  					},
  1243  				}),
  1244  				Action: plans.Update,
  1245  			},
  1246  			expected: `
  1247  [
  1248        - (sensitive value),
  1249      ]
  1250  `,
  1251  		},
  1252  		"list_update_sensitive_element": {
  1253  			diff: computed.Diff{
  1254  				Renderer: List([]computed.Diff{
  1255  					{
  1256  						Renderer: Sensitive(computed.Diff{
  1257  							Renderer: Primitive(0.0, 1.0, cty.Number),
  1258  							Action:   plans.Update,
  1259  						}, true, true),
  1260  						Action: plans.Update,
  1261  					},
  1262  				}),
  1263  				Action: plans.Update,
  1264  			},
  1265  			expected: `
  1266  [
  1267        ~ (sensitive value),
  1268      ]
  1269  `,
  1270  		},
  1271  		"list_update_sensitive_element_status": {
  1272  			diff: computed.Diff{
  1273  				Renderer: List([]computed.Diff{
  1274  					{
  1275  						Renderer: Sensitive(computed.Diff{
  1276  							Renderer: Primitive(1.0, 1.0, cty.Number),
  1277  							Action:   plans.NoOp,
  1278  						}, false, true),
  1279  						Action: plans.Update,
  1280  					},
  1281  				}),
  1282  				Action: plans.Update,
  1283  			},
  1284  			expected: `
  1285  [
  1286        # Warning: this attribute value will be marked as sensitive and will not
  1287        # display in UI output after applying this change. The value is unchanged.
  1288        ~ (sensitive value),
  1289      ]
  1290  `,
  1291  		},
  1292  		"list_create_computed_element": {
  1293  			diff: computed.Diff{
  1294  				Renderer: List([]computed.Diff{
  1295  					{
  1296  						Renderer: Unknown(computed.Diff{}),
  1297  						Action:   plans.Create,
  1298  					},
  1299  				}),
  1300  				Action: plans.Update,
  1301  			},
  1302  			expected: `
  1303  [
  1304        + (known after apply),
  1305      ]
  1306  `,
  1307  		},
  1308  		"list_update_computed_element": {
  1309  			diff: computed.Diff{
  1310  				Renderer: List([]computed.Diff{
  1311  					{
  1312  						Renderer: Unknown(computed.Diff{
  1313  							Renderer: Primitive(0.0, nil, cty.Number),
  1314  							Action:   plans.Delete,
  1315  						}),
  1316  						Action: plans.Update,
  1317  					},
  1318  				}),
  1319  				Action: plans.Update,
  1320  			},
  1321  			expected: `
  1322  [
  1323        ~ 0 -> (known after apply),
  1324      ]
  1325  `,
  1326  		},
  1327  		"set_create_empty": {
  1328  			diff: computed.Diff{
  1329  				Renderer: Set([]computed.Diff{}),
  1330  				Action:   plans.Create,
  1331  			},
  1332  			expected: "[]",
  1333  		},
  1334  		"set_create": {
  1335  			diff: computed.Diff{
  1336  				Renderer: Set([]computed.Diff{
  1337  					{
  1338  						Renderer: Primitive(nil, 1.0, cty.Number),
  1339  						Action:   plans.Create,
  1340  					},
  1341  				}),
  1342  				Action: plans.Create,
  1343  			},
  1344  			expected: `
  1345  [
  1346        + 1,
  1347      ]
  1348  `,
  1349  		},
  1350  		"set_delete_empty": {
  1351  			diff: computed.Diff{
  1352  				Renderer: Set([]computed.Diff{}),
  1353  				Action:   plans.Delete,
  1354  			},
  1355  			expected: "[] -> null",
  1356  		},
  1357  		"set_delete": {
  1358  			diff: computed.Diff{
  1359  				Renderer: Set([]computed.Diff{
  1360  					{
  1361  						Renderer: Primitive(1.0, nil, cty.Number),
  1362  						Action:   plans.Delete,
  1363  					},
  1364  				}),
  1365  				Action: plans.Delete,
  1366  			},
  1367  			expected: `
  1368  [
  1369        - 1,
  1370      ] -> null
  1371  `,
  1372  		},
  1373  		"set_create_element": {
  1374  			diff: computed.Diff{
  1375  				Renderer: Set([]computed.Diff{
  1376  					{
  1377  						Renderer: Primitive(nil, 1.0, cty.Number),
  1378  						Action:   plans.Create,
  1379  					},
  1380  				}),
  1381  				Action: plans.Update,
  1382  			},
  1383  			expected: `
  1384  [
  1385        + 1,
  1386      ]
  1387  `,
  1388  		},
  1389  		"set_update_element": {
  1390  			diff: computed.Diff{
  1391  				Renderer: Set([]computed.Diff{
  1392  					{
  1393  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1394  						Action:   plans.Update,
  1395  					},
  1396  				}),
  1397  				Action: plans.Update,
  1398  			},
  1399  			expected: `
  1400  [
  1401        ~ 0 -> 1,
  1402      ]
  1403  `,
  1404  		},
  1405  		"set_replace_element": {
  1406  			diff: computed.Diff{
  1407  				Renderer: Set([]computed.Diff{
  1408  					{
  1409  						Renderer: Primitive(0.0, nil, cty.Number),
  1410  						Action:   plans.Delete,
  1411  					},
  1412  					{
  1413  						Renderer: Primitive(nil, 1.0, cty.Number),
  1414  						Action:   plans.Create,
  1415  					},
  1416  				}),
  1417  				Action: plans.Update,
  1418  			},
  1419  			expected: `
  1420  [
  1421        - 0,
  1422        + 1,
  1423      ]
  1424  `,
  1425  		},
  1426  		"set_delete_element": {
  1427  			diff: computed.Diff{
  1428  				Renderer: Set([]computed.Diff{
  1429  					{
  1430  						Renderer: Primitive(0.0, nil, cty.Number),
  1431  						Action:   plans.Delete,
  1432  					},
  1433  				}),
  1434  				Action: plans.Update,
  1435  			},
  1436  			expected: `
  1437  [
  1438        - 0,
  1439      ]
  1440  `,
  1441  		},
  1442  		"set_update_forces_replacement": {
  1443  			diff: computed.Diff{
  1444  				Renderer: Set([]computed.Diff{
  1445  					{
  1446  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1447  						Action:   plans.Update,
  1448  					},
  1449  				}),
  1450  				Action:  plans.Update,
  1451  				Replace: true,
  1452  			},
  1453  			expected: `
  1454  [ # forces replacement
  1455        ~ 0 -> 1,
  1456      ]
  1457  `,
  1458  		},
  1459  		"nested_set_update_forces_replacement": {
  1460  			diff: computed.Diff{
  1461  				Renderer: NestedSet([]computed.Diff{
  1462  					{
  1463  						Renderer: Object(map[string]computed.Diff{
  1464  							"attribute_one": {
  1465  								Renderer: Primitive(0.0, 1.0, cty.Number),
  1466  								Action:   plans.Update,
  1467  							},
  1468  						}),
  1469  						Action: plans.Update,
  1470  					},
  1471  				}),
  1472  				Action:  plans.Update,
  1473  				Replace: true,
  1474  			},
  1475  			expected: `
  1476  [
  1477        ~ { # forces replacement
  1478            ~ attribute_one = 0 -> 1
  1479          },
  1480      ]
  1481  `,
  1482  		},
  1483  		"set_update_ignores_unchanged": {
  1484  			diff: computed.Diff{
  1485  				Renderer: Set([]computed.Diff{
  1486  					{
  1487  						Renderer: Primitive(0.0, 0.0, cty.Number),
  1488  						Action:   plans.NoOp,
  1489  					},
  1490  					{
  1491  						Renderer: Primitive(1.0, 1.0, cty.Number),
  1492  						Action:   plans.NoOp,
  1493  					},
  1494  					{
  1495  						Renderer: Primitive(2.0, 5.0, cty.Number),
  1496  						Action:   plans.Update,
  1497  					},
  1498  					{
  1499  						Renderer: Primitive(3.0, 3.0, cty.Number),
  1500  						Action:   plans.NoOp,
  1501  					},
  1502  					{
  1503  						Renderer: Primitive(4.0, 4.0, cty.Number),
  1504  						Action:   plans.NoOp,
  1505  					},
  1506  				}),
  1507  				Action: plans.Update,
  1508  			},
  1509  			expected: `
  1510  [
  1511        ~ 2 -> 5,
  1512          # (4 unchanged elements hidden)
  1513      ]
  1514  `,
  1515  		},
  1516  		"set_create_sensitive_element": {
  1517  			diff: computed.Diff{
  1518  				Renderer: Set([]computed.Diff{
  1519  					{
  1520  						Renderer: Sensitive(computed.Diff{
  1521  							Renderer: Primitive(nil, 1.0, cty.Number),
  1522  							Action:   plans.Create,
  1523  						}, false, true),
  1524  						Action: plans.Create,
  1525  					},
  1526  				}),
  1527  				Action: plans.Update,
  1528  			},
  1529  			expected: `
  1530  [
  1531        + (sensitive value),
  1532      ]
  1533  `,
  1534  		},
  1535  		"set_delete_sensitive_element": {
  1536  			diff: computed.Diff{
  1537  				Renderer: Set([]computed.Diff{
  1538  					{
  1539  						Renderer: Sensitive(computed.Diff{
  1540  							Renderer: Primitive(1.0, nil, cty.Number),
  1541  							Action:   plans.Delete,
  1542  						}, false, true),
  1543  						Action: plans.Delete,
  1544  					},
  1545  				}),
  1546  				Action: plans.Update,
  1547  			},
  1548  			expected: `
  1549  [
  1550        - (sensitive value),
  1551      ]
  1552  `,
  1553  		},
  1554  		"set_update_sensitive_element": {
  1555  			diff: computed.Diff{
  1556  				Renderer: Set([]computed.Diff{
  1557  					{
  1558  						Renderer: Sensitive(computed.Diff{
  1559  							Renderer: Primitive(0.0, 1.0, cty.Number),
  1560  							Action:   plans.Update,
  1561  						}, true, true),
  1562  						Action: plans.Update,
  1563  					},
  1564  				}),
  1565  				Action: plans.Update,
  1566  			},
  1567  			expected: `
  1568  [
  1569        ~ (sensitive value),
  1570      ]
  1571  `,
  1572  		},
  1573  		"set_update_sensitive_element_status": {
  1574  			diff: computed.Diff{
  1575  				Renderer: Set([]computed.Diff{
  1576  					{
  1577  						Renderer: Sensitive(computed.Diff{
  1578  							Renderer: Primitive(1.0, 2.0, cty.Number),
  1579  							Action:   plans.Update,
  1580  						}, false, true),
  1581  						Action: plans.Update,
  1582  					},
  1583  				}),
  1584  				Action: plans.Update,
  1585  			},
  1586  			expected: `
  1587  [
  1588        # Warning: this attribute value will be marked as sensitive and will not
  1589        # display in UI output after applying this change.
  1590        ~ (sensitive value),
  1591      ]
  1592  `,
  1593  		},
  1594  		"set_create_computed_element": {
  1595  			diff: computed.Diff{
  1596  				Renderer: Set([]computed.Diff{
  1597  					{
  1598  						Renderer: Unknown(computed.Diff{}),
  1599  						Action:   plans.Create,
  1600  					},
  1601  				}),
  1602  				Action: plans.Update,
  1603  			},
  1604  			expected: `
  1605  [
  1606        + (known after apply),
  1607      ]
  1608  `,
  1609  		},
  1610  		"set_update_computed_element": {
  1611  			diff: computed.Diff{
  1612  				Renderer: Set([]computed.Diff{
  1613  					{
  1614  						Renderer: Unknown(computed.Diff{
  1615  							Renderer: Primitive(0.0, nil, cty.Number),
  1616  							Action:   plans.Delete,
  1617  						}),
  1618  						Action: plans.Update,
  1619  					},
  1620  				}),
  1621  				Action: plans.Update,
  1622  			},
  1623  			expected: `
  1624  [
  1625        ~ 0 -> (known after apply),
  1626      ]
  1627  `,
  1628  		},
  1629  		"create_empty_block": {
  1630  			diff: computed.Diff{
  1631  				Renderer: Block(nil, Blocks{}),
  1632  				Action:   plans.Create,
  1633  			},
  1634  			expected: "{}",
  1635  		},
  1636  		"create_populated_block": {
  1637  			diff: computed.Diff{
  1638  				Renderer: Block(map[string]computed.Diff{
  1639  					"string": {
  1640  						Renderer: Primitive(nil, "root", cty.String),
  1641  						Action:   plans.Create,
  1642  					},
  1643  					"boolean": {
  1644  						Renderer: Primitive(nil, true, cty.Bool),
  1645  						Action:   plans.Create,
  1646  					},
  1647  				}, Blocks{
  1648  					SingleBlocks: map[string]computed.Diff{
  1649  						"nested_block": {
  1650  							Renderer: Block(map[string]computed.Diff{
  1651  								"string": {
  1652  									Renderer: Primitive(nil, "one", cty.String),
  1653  									Action:   plans.Create,
  1654  								},
  1655  							}, Blocks{}),
  1656  							Action: plans.Create,
  1657  						},
  1658  						"nested_block_two": {
  1659  							Renderer: Block(map[string]computed.Diff{
  1660  								"string": {
  1661  									Renderer: Primitive(nil, "two", cty.String),
  1662  									Action:   plans.Create,
  1663  								},
  1664  							}, Blocks{}),
  1665  							Action: plans.Create,
  1666  						},
  1667  					},
  1668  				}),
  1669  				Action: plans.Create,
  1670  			},
  1671  			expected: `
  1672  {
  1673        + boolean = true
  1674        + string  = "root"
  1675  
  1676        + nested_block {
  1677            + string = "one"
  1678          }
  1679  
  1680        + nested_block_two {
  1681            + string = "two"
  1682          }
  1683      }`,
  1684  		},
  1685  		"update_empty_block": {
  1686  			diff: computed.Diff{
  1687  				Renderer: Block(map[string]computed.Diff{
  1688  					"string": {
  1689  						Renderer: Primitive(nil, "root", cty.String),
  1690  						Action:   plans.Create,
  1691  					},
  1692  					"boolean": {
  1693  						Renderer: Primitive(nil, true, cty.Bool),
  1694  						Action:   plans.Create,
  1695  					},
  1696  				}, Blocks{
  1697  					SingleBlocks: map[string]computed.Diff{
  1698  						"nested_block": {
  1699  
  1700  							Renderer: Block(map[string]computed.Diff{
  1701  								"string": {
  1702  									Renderer: Primitive(nil, "one", cty.String),
  1703  									Action:   plans.Create,
  1704  								},
  1705  							}, Blocks{}),
  1706  							Action: plans.Create,
  1707  						},
  1708  						"nested_block_two": {
  1709  
  1710  							Renderer: Block(map[string]computed.Diff{
  1711  								"string": {
  1712  									Renderer: Primitive(nil, "two", cty.String),
  1713  									Action:   plans.Create,
  1714  								},
  1715  							}, Blocks{}),
  1716  							Action: plans.Create,
  1717  						},
  1718  					},
  1719  				}),
  1720  				Action: plans.Update,
  1721  			},
  1722  			expected: `
  1723  {
  1724        + boolean = true
  1725        + string  = "root"
  1726  
  1727        + nested_block {
  1728            + string = "one"
  1729          }
  1730  
  1731        + nested_block_two {
  1732            + string = "two"
  1733          }
  1734      }`,
  1735  		},
  1736  		"update_populated_block": {
  1737  			diff: computed.Diff{
  1738  				Renderer: Block(map[string]computed.Diff{
  1739  					"string": {
  1740  						Renderer: Primitive(nil, "root", cty.String),
  1741  						Action:   plans.Create,
  1742  					},
  1743  					"boolean": {
  1744  						Renderer: Primitive(false, true, cty.Bool),
  1745  						Action:   plans.Update,
  1746  					},
  1747  				}, Blocks{
  1748  					SingleBlocks: map[string]computed.Diff{
  1749  						"nested_block": {
  1750  							Renderer: Block(map[string]computed.Diff{
  1751  								"string": {
  1752  									Renderer: Primitive(nil, "one", cty.String),
  1753  									Action:   plans.NoOp,
  1754  								},
  1755  							}, Blocks{}),
  1756  							Action: plans.NoOp,
  1757  						},
  1758  						"nested_block_two": {
  1759  							Renderer: Block(map[string]computed.Diff{
  1760  								"string": {
  1761  									Renderer: Primitive(nil, "two", cty.String),
  1762  									Action:   plans.Create,
  1763  								},
  1764  							}, Blocks{}),
  1765  							Action: plans.Create,
  1766  						},
  1767  					},
  1768  				}),
  1769  				Action: plans.Update,
  1770  			},
  1771  			expected: `
  1772  {
  1773        ~ boolean = false -> true
  1774        + string  = "root"
  1775  
  1776        + nested_block_two {
  1777            + string = "two"
  1778          }
  1779  
  1780          # (1 unchanged block hidden)
  1781      }`,
  1782  		},
  1783  		"clear_populated_block": {
  1784  			diff: computed.Diff{
  1785  				Renderer: Block(map[string]computed.Diff{
  1786  					"string": {
  1787  						Renderer: Primitive("root", nil, cty.String),
  1788  						Action:   plans.Delete,
  1789  					},
  1790  					"boolean": {
  1791  						Renderer: Primitive(true, nil, cty.Bool),
  1792  						Action:   plans.Delete,
  1793  					},
  1794  				}, Blocks{
  1795  					SingleBlocks: map[string]computed.Diff{
  1796  						"nested_block": {
  1797  							Renderer: Block(map[string]computed.Diff{
  1798  								"string": {
  1799  									Renderer: Primitive("one", nil, cty.String),
  1800  									Action:   plans.Delete,
  1801  								},
  1802  							}, Blocks{}),
  1803  							Action: plans.Delete,
  1804  						},
  1805  						"nested_block_two": {
  1806  							Renderer: Block(map[string]computed.Diff{
  1807  								"string": {
  1808  									Renderer: Primitive("two", nil, cty.String),
  1809  									Action:   plans.Delete,
  1810  								},
  1811  							}, Blocks{}),
  1812  							Action: plans.Delete,
  1813  						},
  1814  					},
  1815  				}),
  1816  				Action: plans.Update,
  1817  			},
  1818  			expected: `
  1819  {
  1820        - boolean = true -> null
  1821        - string  = "root" -> null
  1822  
  1823        - nested_block {
  1824            - string = "one" -> null
  1825          }
  1826  
  1827        - nested_block_two {
  1828            - string = "two" -> null
  1829          }
  1830      }`,
  1831  		},
  1832  		"delete_populated_block": {
  1833  			diff: computed.Diff{
  1834  				Renderer: Block(map[string]computed.Diff{
  1835  					"string": {
  1836  						Renderer: Primitive("root", nil, cty.String),
  1837  						Action:   plans.Delete,
  1838  					},
  1839  					"boolean": {
  1840  						Renderer: Primitive(true, nil, cty.Bool),
  1841  						Action:   plans.Delete,
  1842  					},
  1843  				}, Blocks{
  1844  					SingleBlocks: map[string]computed.Diff{
  1845  						"nested_block": {
  1846  							Renderer: Block(map[string]computed.Diff{
  1847  								"string": {
  1848  									Renderer: Primitive("one", nil, cty.String),
  1849  									Action:   plans.Delete,
  1850  								},
  1851  							}, Blocks{}),
  1852  							Action: plans.Delete,
  1853  						},
  1854  						"nested_block_two": {
  1855  							Renderer: Block(map[string]computed.Diff{
  1856  								"string": {
  1857  									Renderer: Primitive("two", nil, cty.String),
  1858  									Action:   plans.Delete,
  1859  								},
  1860  							}, Blocks{}),
  1861  							Action: plans.Delete,
  1862  						},
  1863  					},
  1864  				}),
  1865  				Action: plans.Delete,
  1866  			},
  1867  			expected: `
  1868  {
  1869        - boolean = true -> null
  1870        - string  = "root" -> null
  1871  
  1872        - nested_block {
  1873            - string = "one" -> null
  1874          }
  1875  
  1876        - nested_block_two {
  1877            - string = "two" -> null
  1878          }
  1879      }`,
  1880  		},
  1881  		"list_block_update": {
  1882  			diff: computed.Diff{
  1883  				Renderer: Block(
  1884  					nil,
  1885  					Blocks{
  1886  						ListBlocks: map[string][]computed.Diff{
  1887  							"list_blocks": {
  1888  								{
  1889  									Renderer: Block(map[string]computed.Diff{
  1890  										"number": {
  1891  											Renderer: Primitive(1.0, 2.0, cty.Number),
  1892  											Action:   plans.Update,
  1893  										},
  1894  										"string": {
  1895  											Renderer: Primitive(nil, "new", cty.String),
  1896  											Action:   plans.Create,
  1897  										},
  1898  									}, Blocks{}),
  1899  									Action: plans.Update,
  1900  								},
  1901  								{
  1902  									Renderer: Block(map[string]computed.Diff{
  1903  										"number": {
  1904  											Renderer: Primitive(1.0, nil, cty.Number),
  1905  											Action:   plans.Delete,
  1906  										},
  1907  										"string": {
  1908  											Renderer: Primitive("old", "new", cty.String),
  1909  											Action:   plans.Update,
  1910  										},
  1911  									}, Blocks{}),
  1912  									Action: plans.Update,
  1913  								},
  1914  							},
  1915  						},
  1916  					}),
  1917  			},
  1918  			expected: `
  1919  {
  1920        ~ list_blocks {
  1921            ~ number = 1 -> 2
  1922            + string = "new"
  1923          }
  1924        ~ list_blocks {
  1925            - number = 1 -> null
  1926            ~ string = "old" -> "new"
  1927          }
  1928      }`,
  1929  		},
  1930  		"set_block_update": {
  1931  			diff: computed.Diff{
  1932  				Renderer: Block(
  1933  					nil,
  1934  					Blocks{
  1935  						SetBlocks: map[string][]computed.Diff{
  1936  							"set_blocks": {
  1937  								{
  1938  									Renderer: Block(map[string]computed.Diff{
  1939  										"number": {
  1940  											Renderer: Primitive(1.0, 2.0, cty.Number),
  1941  											Action:   plans.Update,
  1942  										},
  1943  										"string": {
  1944  											Renderer: Primitive(nil, "new", cty.String),
  1945  											Action:   plans.Create,
  1946  										},
  1947  									}, Blocks{}),
  1948  									Action: plans.Update,
  1949  								},
  1950  								{
  1951  									Renderer: Block(map[string]computed.Diff{
  1952  										"number": {
  1953  											Renderer: Primitive(1.0, nil, cty.Number),
  1954  											Action:   plans.Delete,
  1955  										},
  1956  										"string": {
  1957  											Renderer: Primitive("old", "new", cty.String),
  1958  											Action:   plans.Update,
  1959  										},
  1960  									}, Blocks{}),
  1961  									Action: plans.Update,
  1962  								},
  1963  							},
  1964  						},
  1965  					}),
  1966  			},
  1967  			expected: `
  1968  {
  1969        ~ set_blocks {
  1970            ~ number = 1 -> 2
  1971            + string = "new"
  1972          }
  1973        ~ set_blocks {
  1974            - number = 1 -> null
  1975            ~ string = "old" -> "new"
  1976          }
  1977      }`,
  1978  		},
  1979  		"map_block_update": {
  1980  			diff: computed.Diff{
  1981  				Renderer: Block(
  1982  					nil,
  1983  					Blocks{
  1984  						MapBlocks: map[string]map[string]computed.Diff{
  1985  							"list_blocks": {
  1986  								"key_one": {
  1987  									Renderer: Block(map[string]computed.Diff{
  1988  										"number": {
  1989  											Renderer: Primitive(1.0, 2.0, cty.Number),
  1990  											Action:   plans.Update,
  1991  										},
  1992  										"string": {
  1993  											Renderer: Primitive(nil, "new", cty.String),
  1994  											Action:   plans.Create,
  1995  										},
  1996  									}, Blocks{}),
  1997  									Action: plans.Update,
  1998  								},
  1999  								"key:two": {
  2000  									Renderer: Block(map[string]computed.Diff{
  2001  										"number": {
  2002  											Renderer: Primitive(1.0, nil, cty.Number),
  2003  											Action:   plans.Delete,
  2004  										},
  2005  										"string": {
  2006  											Renderer: Primitive("old", "new", cty.String),
  2007  											Action:   plans.Update,
  2008  										},
  2009  									}, Blocks{}),
  2010  									Action: plans.Update,
  2011  								},
  2012  							},
  2013  						},
  2014  					}),
  2015  			},
  2016  			expected: `
  2017  {
  2018        ~ list_blocks "key:two" {
  2019            - number = 1 -> null
  2020            ~ string = "old" -> "new"
  2021          }
  2022        ~ list_blocks "key_one" {
  2023            ~ number = 1 -> 2
  2024            + string = "new"
  2025          }
  2026      }
  2027  `,
  2028  		},
  2029  		"sensitive_block": {
  2030  			diff: computed.Diff{
  2031  				Renderer: SensitiveBlock(computed.Diff{
  2032  					Renderer: Block(nil, Blocks{}),
  2033  					Action:   plans.NoOp,
  2034  				}, true, true),
  2035  				Action: plans.Update,
  2036  			},
  2037  			expected: `
  2038  {
  2039        # At least one attribute in this block is (or was) sensitive,
  2040        # so its contents will not be displayed.
  2041      }
  2042  `,
  2043  		},
  2044  		"delete_empty_block": {
  2045  			diff: computed.Diff{
  2046  				Renderer: Block(nil, Blocks{}),
  2047  				Action:   plans.Delete,
  2048  			},
  2049  			expected: "{}",
  2050  		},
  2051  		"block_escapes_keys": {
  2052  			diff: computed.Diff{
  2053  				Renderer: Block(map[string]computed.Diff{
  2054  					"attribute_one": {
  2055  						Renderer: Primitive(1.0, 2.0, cty.Number),
  2056  						Action:   plans.Update,
  2057  					},
  2058  					"attribute:two": {
  2059  						Renderer: Primitive(2.0, 3.0, cty.Number),
  2060  						Action:   plans.Update,
  2061  					},
  2062  					"attribute_six": {
  2063  						Renderer: Primitive(3.0, 4.0, cty.Number),
  2064  						Action:   plans.Update,
  2065  					},
  2066  				}, Blocks{
  2067  					SingleBlocks: map[string]computed.Diff{
  2068  						"nested_block:one": {
  2069  							Renderer: Block(map[string]computed.Diff{
  2070  								"string": {
  2071  									Renderer: Primitive("one", "four", cty.String),
  2072  									Action:   plans.Update,
  2073  								},
  2074  							}, Blocks{}),
  2075  							Action: plans.Update,
  2076  						},
  2077  						"nested_block_two": {
  2078  							Renderer: Block(map[string]computed.Diff{
  2079  								"string": {
  2080  									Renderer: Primitive("two", "three", cty.String),
  2081  									Action:   plans.Update,
  2082  								},
  2083  							}, Blocks{}),
  2084  							Action: plans.Update,
  2085  						},
  2086  					},
  2087  				}),
  2088  				Action: plans.Update,
  2089  			},
  2090  			expected: `
  2091  {
  2092        ~ "attribute:two" = 2 -> 3
  2093        ~ attribute_one   = 1 -> 2
  2094        ~ attribute_six   = 3 -> 4
  2095  
  2096        ~ "nested_block:one" {
  2097            ~ string = "one" -> "four"
  2098          }
  2099  
  2100        ~ nested_block_two {
  2101            ~ string = "two" -> "three"
  2102          }
  2103      }`,
  2104  		},
  2105  		"block_always_includes_important_attributes": {
  2106  			diff: computed.Diff{
  2107  				Renderer: Block(map[string]computed.Diff{
  2108  					"id": {
  2109  						Renderer: Primitive("root", "root", cty.String),
  2110  						Action:   plans.NoOp,
  2111  					},
  2112  					"boolean": {
  2113  						Renderer: Primitive(false, false, cty.Bool),
  2114  						Action:   plans.NoOp,
  2115  					},
  2116  				}, Blocks{
  2117  					SingleBlocks: map[string]computed.Diff{
  2118  						"nested_block": {
  2119  							Renderer: Block(map[string]computed.Diff{
  2120  								"string": {
  2121  									Renderer: Primitive("one", "one", cty.String),
  2122  									Action:   plans.NoOp,
  2123  								},
  2124  							}, Blocks{}),
  2125  							Action: plans.NoOp,
  2126  						},
  2127  						"nested_block_two": {
  2128  							Renderer: Block(map[string]computed.Diff{
  2129  								"string": {
  2130  									Renderer: Primitive("two", "two", cty.String),
  2131  									Action:   plans.NoOp,
  2132  								},
  2133  							}, Blocks{}),
  2134  							Action: plans.NoOp,
  2135  						},
  2136  					},
  2137  				}),
  2138  				Action: plans.NoOp,
  2139  			},
  2140  			expected: `
  2141  {
  2142          id      = "root"
  2143          # (1 unchanged attribute hidden)
  2144  
  2145          # (2 unchanged blocks hidden)
  2146      }`,
  2147  		},
  2148  		"output_map_to_list": {
  2149  			diff: computed.Diff{
  2150  				Renderer: TypeChange(computed.Diff{
  2151  					Renderer: Map(map[string]computed.Diff{
  2152  						"element_one": {
  2153  							Renderer: Primitive(0.0, nil, cty.Number),
  2154  							Action:   plans.Delete,
  2155  						},
  2156  						"element_two": {
  2157  							Renderer: Primitive(1.0, nil, cty.Number),
  2158  							Action:   plans.Delete,
  2159  						},
  2160  					}),
  2161  					Action: plans.Delete,
  2162  				}, computed.Diff{
  2163  					Renderer: List([]computed.Diff{
  2164  						{
  2165  							Renderer: Primitive(nil, 0.0, cty.Number),
  2166  							Action:   plans.Create,
  2167  						},
  2168  						{
  2169  							Renderer: Primitive(nil, 1.0, cty.Number),
  2170  							Action:   plans.Create,
  2171  						},
  2172  					}),
  2173  					Action: plans.Create,
  2174  				}),
  2175  			},
  2176  			expected: `
  2177  {
  2178        - "element_one" = 0
  2179        - "element_two" = 1
  2180      } -> [
  2181        + 0,
  2182        + 1,
  2183      ]
  2184  `,
  2185  		},
  2186  		"json_string_no_symbols": {
  2187  			diff: computed.Diff{
  2188  				Renderer: Primitive("{\"key\":\"value\"}", "{\"key\":\"value\"}", cty.String),
  2189  				Action:   plans.NoOp,
  2190  			},
  2191  			opts: computed.RenderHumanOpts{
  2192  				HideDiffActionSymbols: true,
  2193  				ShowUnchangedChildren: true,
  2194  			},
  2195  			expected: `
  2196  jsonencode(
  2197      {
  2198          key = "value"
  2199      }
  2200  )
  2201  `,
  2202  		},
  2203  	}
  2204  	for name, tc := range tcs {
  2205  		t.Run(name, func(t *testing.T) {
  2206  
  2207  			opts := tc.opts.Clone()
  2208  			opts.Colorize = &colorize
  2209  
  2210  			expected := strings.TrimSpace(tc.expected)
  2211  			actual := tc.diff.RenderHuman(0, opts)
  2212  			if diff := cmp.Diff(expected, actual); len(diff) > 0 {
  2213  				t.Fatalf("\nexpected:\n%s\nactual:\n%s\ndiff:\n%s\n", expected, actual, diff)
  2214  			}
  2215  		})
  2216  	}
  2217  }